public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v12 05/11] Add pg_ls_dir_metadata to list a dir with file metadata..
68+ messages / 3 participants
[nested] [flat]
* [PATCH v12 05/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-08 22:15 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-08 22:15 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 17 +++
src/backend/utils/adt/genfile.c | 194 ++++++++++++++++++--------------
src/include/catalog/pg_proc.dat | 6 +
3 files changed, 132 insertions(+), 85 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..4b966ed847 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..98ab9a2b92 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,14 +36,23 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
-typedef struct
-{
- char *location;
- DIR *dirdesc;
- bool include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
/*
* Convert a "text" filename argument to C string, and check it's allowable.
*
@@ -447,67 +456,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- struct dirent *de;
- directory_fctx *fctx;
- MemoryContext oldcontext;
-
- if (SRF_IS_FIRSTCALL())
- {
- 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);
- }
-
- funcctx = SRF_PERCALL_SETUP();
- fctx = (directory_fctx *) funcctx->user_fctx;
-
- while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
- {
- if (!fctx->include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
- }
-
- FreeDir(fctx->dirdesc);
-
- SRF_RETURN_DONE(funcctx);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -520,7 +471,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -530,7 +483,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -539,6 +492,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +533,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ } else {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +563,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
{
tuplestore_donestoring(tupstore);
return (Datum) 0;
@@ -584,13 +573,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -600,13 +596,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +631,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -646,7 +656,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
}
/*
@@ -675,5 +686,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+ proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
--
2.17.0
--wIc/V6YLA2QdyfT4
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v12-0006-Show-links-to-dirs-with-isdir-false.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v13 3/8] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-08 22:15 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-08 22:15 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Change to use lstat() to allow pg_ls_dir_recurse to avoid infinite recursion.
That means:
- links to dirs are shown with isdir=false;
- timestamps shown are those of the link;
- changed pg_stat_file for consistency;
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++-
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 218 +++++++++++++++------------
src/include/catalog/pg_proc.dat | 6 +
4 files changed, 150 insertions(+), 94 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..68c7327e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
@@ -21528,7 +21545,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
size, last accessed time stamp, last modified time stamp,
last file status change time stamp (Unix platforms only),
file creation time stamp (Windows only), and a <type>boolean</type>
- indicating if it is a directory (or a symbolic link to a directory).
+ indicating if it is a directory (and not a symbolic link to a directory).
Typical usages include:
<programlisting>
SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..05a644a7c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1435,6 +1435,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 01185f218b..4b70a00a35 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -370,7 +385,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
filename = convert_and_check_filename(filename_t);
- if (stat(filename, &fst) < 0)
+ if (lstat(filename, &fst) < 0)
{
if (missing_ok && errno == ENOENT)
PG_RETURN_NULL();
@@ -413,6 +428,10 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ if (pgwin32_is_junction(path)) /* Links are not directories */
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +459,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +474,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +486,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +495,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +535,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ } else {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,36 +565,67 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
+ {
+ tuplestore_donestoring(tupstore);
return (Datum) 0;
+ }
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
- if (stat(path, &attrib) < 0)
+ if (lstat(path, &attrib) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links are not directories */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +639,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -646,7 +664,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
}
/*
@@ -675,5 +694,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+ proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
--
2.17.0
--E0h0CbphJD8hN+Gf
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v13-0004-pg_ls_tmpdir-to-show-isdir-argument.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v9 04/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-08 22:15 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-08 22:15 UTC (permalink / raw)
Need catversion bumped?
---
src/backend/utils/adt/genfile.c | 51 ++++++++++++++++++++++++++-------
src/include/catalog/pg_proc.dat | 6 ++++
2 files changed, 47 insertions(+), 10 deletions(-)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 897b11a77d..4699aea568 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -524,7 +524,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
/* Generic function to return a directory listing of files */
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok, bool dir_ok)
{
FuncCallContext *funcctx;
struct dirent *de;
@@ -540,13 +540,17 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
fctx = palloc(sizeof(directory_fctx));
- tupdesc = CreateTemplateTupleDesc(3);
+ tupdesc = CreateTemplateTupleDesc(dir_ok ? 4:3);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size",
INT8OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification",
TIMESTAMPTZOID, -1, 0);
+ if (dir_ok)
+ TupleDescInitEntry(tupdesc, (AttrNumber) 4, "isdir",
+ BOOLOID, -1, 0);
+
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
fctx->location = pstrdup(dir);
@@ -575,8 +579,8 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
HeapTuple tuple;
@@ -592,13 +596,21 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (!dir_ok)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ /* Ignore anything but regular files */
continue;
values[0] = CStringGetTextDatum(de->d_name);
values[1] = Int64GetDatum((int64) attrib.st_size);
values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (dir_ok)
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+
memset(nulls, 0, sizeof(nulls));
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
@@ -613,14 +625,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, false, false);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, false, false);
}
/*
@@ -638,7 +650,7 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path, true, false);
}
/*
@@ -667,5 +679,24 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true, false);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+ bool missing_ok = false;
+ bool include_dot_dirs = false;
+
+ if (!PG_ARGISNULL(1))
+ missing_ok = PG_GETARG_BOOL(1);
+ if (!PG_ARGISNULL(2))
+ /* XXX: Not implemented */
+ include_dot_dirs = PG_GETARG_BOOL(2);
+
+ return pg_ls_dir_files(fcinfo, dirname, missing_ok, true);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+ proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
--
2.17.0
--32u276st3Jlj2kUU
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v9-0005-Add-tests-exercizing-pg_ls_tmpdir.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v10 4/9] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-08 22:15 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-08 22:15 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
src/backend/utils/adt/genfile.c | 184 +++++++++++++++++---------------
src/include/catalog/pg_proc.dat | 6 ++
2 files changed, 106 insertions(+), 84 deletions(-)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..4f437573ab 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,13 +36,15 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
-typedef struct
-{
- char *location;
- DIR *dirdesc;
- bool include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+#define FLAG_ISDIR (1<<0) /* Show column: isdir */
+#define FLAG_METADATA (1<<1) /* Show columns: mtime, size */
+#define FLAG_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define FLAG_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define FLAG_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define FLAG_SKIP_DIRS (1<<5) /* Do not show directories */
+#define FLAG_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -447,67 +449,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- struct dirent *de;
- directory_fctx *fctx;
- MemoryContext oldcontext;
-
- if (SRF_IS_FIRSTCALL())
- {
- 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);
- }
-
- funcctx = SRF_PERCALL_SETUP();
- fctx = (directory_fctx *) funcctx->user_fctx;
-
- while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
- {
- if (!fctx->include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
- }
-
- FreeDir(fctx->dirdesc);
-
- SRF_RETURN_DONE(funcctx);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
}
/*
@@ -520,7 +464,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, FLAG_SKIP_DOT_DIRS);
}
/*
@@ -530,7 +476,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -539,6 +485,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&FLAG_ISDIR) || (flags&FLAG_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&FLAG_ISDIR) || !(flags&FLAG_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= FLAG_MISSING_OK;
+ else
+ flags &= ~FLAG_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~FLAG_SKIP_DOT_DIRS;
+ else
+ flags |= FLAG_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +526,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags&FLAG_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ } else {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +556,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags&FLAG_MISSING_OK && errno == ENOENT)
{
tuplestore_donestoring(tupstore);
return (Datum) 0;
@@ -584,13 +566,19 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
+ if (flags&FLAG_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
/* Skip hidden files */
- if (de->d_name[0] == '.')
+ if (flags&FLAG_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -600,13 +588,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags&FLAG_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags&FLAG_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & FLAG_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & FLAG_ISDIR)
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +622,16 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory,
+ FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR,
+ FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
}
/*
@@ -646,7 +649,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
}
/*
@@ -675,5 +679,17 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname, FLAG_METADATA|FLAG_SKIP_SPECIAL|FLAG_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+ proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
--
2.17.0
--rCwQ2Y43eQY6RBgR
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v10-0005-pg_ls_tmpdir-to-show-isdir-argument.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v11 4/9] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-08 22:15 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-08 22:15 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 17 +++
src/backend/utils/adt/genfile.c | 190 ++++++++++++++++++--------------
src/include/catalog/pg_proc.dat | 6 +
3 files changed, 129 insertions(+), 84 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..dfea9f8061 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index bcf9bd1b97..b105363903 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,14 +36,23 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
-typedef struct
-{
- char *location;
- DIR *dirdesc;
- bool include_dot_dirs;
-} directory_fctx;
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
+
/*
* Convert a "text" filename argument to C string, and check it's allowable.
*
@@ -447,67 +456,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- struct dirent *de;
- directory_fctx *fctx;
- MemoryContext oldcontext;
-
- if (SRF_IS_FIRSTCALL())
- {
- 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);
- }
-
- funcctx = SRF_PERCALL_SETUP();
- fctx = (directory_fctx *) funcctx->user_fctx;
-
- while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
- {
- if (!fctx->include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name));
- }
-
- FreeDir(fctx->dirdesc);
-
- SRF_RETURN_DONE(funcctx);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -520,7 +471,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -530,7 +483,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -539,6 +492,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -554,8 +533,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ } else {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -574,7 +563,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
{
tuplestore_donestoring(tupstore);
return (Datum) 0;
@@ -584,13 +573,19 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
/* Skip hidden files */
- if (de->d_name[0] == '.')
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -600,13 +595,26 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +629,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -646,7 +654,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
}
/*
@@ -675,5 +684,18 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC|LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA|LS_DIR_SKIP_SPECIAL|LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..0a1859f709 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,12 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool bool',
+ proallargtypes => '{text,bool,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,dir_ok,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
--
2.17.0
--oTHb8nViIGeoXxdp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v11-0005-pg_ls_tmpdir-to-show-isdir-argument.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v32 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 239 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 225 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b5c1befe627..7948e6ec06c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25945,6 +25945,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 3a4fa9091b1..5eb6cc8572c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,6 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 027ed864001..0728547ec2b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -719,7 +738,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -728,7 +775,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -737,7 +784,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -762,5 +809,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4d992dc2241..c99e1633bbf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11645,6 +11645,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 830de507e7c..b3a9d11b5c0 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -267,6 +267,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 864f4b6e208..21f7f19b67f 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -16,6 +16,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 422a1369aeb..ca946d08bde 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -99,6 +99,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 92076db9a13..291ca029c14 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -16,6 +16,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.17.0
--Bne5rrxQd65beI7a
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v32-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v31 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 239 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 225 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d36479d86d..e0099e77df 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25860,6 +25860,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index f6789025a5..a45bac89ff 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,6 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 027ed86400..0728547ec2 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -719,7 +738,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -728,7 +775,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -737,7 +784,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -762,5 +809,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e934361dc3..39fbaacbc9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11645,6 +11645,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 830de507e7..b3a9d11b5c 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -267,6 +267,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index cb9774ecc8..e69fa17004 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index e7629d470e..c2f6d64c5c 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 422a1369ae..ca946d08bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -99,6 +99,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
--
2.17.0
--qZVVwWJgpX9Jzs7f
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v31-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v33 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 240 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 226 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d32b414e04f..14b8fba3123 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25958,6 +25958,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index fd1421788e6..2ffc2f9d1a6 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -702,6 +702,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 542bbacaa24..3921723fda6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -645,13 +642,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+ else
+#endif
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +684,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -690,7 +709,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -719,7 +739,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -728,7 +776,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -737,7 +785,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -762,5 +810,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0859dc81cac..b58987b713c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11661,6 +11661,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b7264af3ecb..a627839bc28 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -309,6 +309,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 2dfbcfdebe1..c6b9074ab02 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 169f66416c3..bfa8e309e9a 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -109,6 +109,17 @@ select * from pg_ls_tmpdir() limit 0;
select * from pg_ls_waldir() limit 0;
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 896f05cea32..3aadac8b611 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -23,6 +23,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.17.1
--9CzcV6dAFIr7O1Ie
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v33-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v30 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 233 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 222 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7a830f0684..dd9b1afde4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25848,6 +25848,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index a416e94d37..649a78605a 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -698,6 +698,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c436d9318b..8efb798b2f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -451,6 +466,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -478,79 +498,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -563,17 +513,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -582,6 +534,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -596,8 +574,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -616,20 +606,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -644,13 +641,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -664,14 +682,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -689,7 +707,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -718,5 +737,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fde251fa4f..b405aa341c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11547,6 +11547,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--ZwgA9U+XZDXt4+m+
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v30-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v25 04/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 231 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 221 insertions(+), 92 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371..226923a134 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25750,6 +25750,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c210bc..93d4f1e4a6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,6 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..7351615f8a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
* Generic function to return a directory listing of files.
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fc2202b843..8e88c549d9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10976,6 +10976,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--SBikYMzjhZGK9d4p
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v25-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v26 04/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 233 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 222 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2707e757ca..79d30a1568 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25799,6 +25799,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c210bc..93d4f1e4a6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1506,6 +1506,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..198a99391a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 22970f46cd..17b03d1abd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11266,6 +11266,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--yKkOmjQZXRsvHRX8
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v26-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v27 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 233 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 222 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 977b75a531..76ae2f2dd8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25793,6 +25793,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5f2541d316..fcd050b752 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1607,6 +1607,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 322152ebd9..f59f853983 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -719,5 +738,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4309fa40dd..b85bee2d1e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11517,6 +11517,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--19uQFt6ulqmgNgg1
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v27-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v28 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 233 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 222 insertions(+), 93 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index aa0dcde886..507a6d73f8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25821,6 +25821,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 451db2ee0a..dd84b0f11a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1632,6 +1632,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 322152ebd9..f59f853983 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -452,6 +467,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -479,79 +499,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -564,17 +514,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -583,6 +535,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -597,8 +575,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -617,20 +607,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -645,13 +642,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -665,14 +683,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -690,7 +708,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -719,5 +738,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f4957653ae..550fbf734a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11534,6 +11534,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ea0fc48dbd..b4112ec298 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -238,6 +238,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index c133e73499..a32212be04 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1bbe7e0323..5f7ce3d09f 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index eb6ac12ab4..c169e527d9 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -77,6 +77,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--vk/v8fjDPiDepTtA
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v28-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v22 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7ef2ec9972..34c92903d8 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25747,6 +25747,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c6dd084fbc..169602c1b6 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1509,6 +1509,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..054b8d4b1a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 24ec2cfed6..02b1436d83 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10964,6 +10964,18 @@
proallargtypes => '{regclass,text,text,int8}', proargmodes => '{i,i,o,o}',
proargnames => '{relation,fork,path,failed_block_num}',
prosrc => 'pg_relation_check_pages' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--d6Gm4EdcadzBjdND
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v22-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v23 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b1dc264f6..0a924fdcc3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25738,6 +25738,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c4b6..e7647787cf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1503,6 +1503,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..6f003bbf67 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c01da4bf01..add3c7dc62 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10971,6 +10971,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--mhjHhnbe5PrRcwjY
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v23-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v24 04/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 231 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 221 insertions(+), 92 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a668..5e267c4f8d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25757,6 +25757,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c4b6..e7647787cf 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1503,6 +1503,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..7351615f8a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,17 +511,19 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
* Generic function to return a directory listing of files.
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e7fbda9f81..f35a684dcf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10975,6 +10975,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index edbfd9abc1..fd7c3c791f 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -225,6 +225,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index b32a3a4b74..64d9d70874 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -68,6 +68,17 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--mPTHnM80CEnHQ2WJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v24-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v19 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7c450ea29..2931f9d7d5 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25711,6 +25711,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 5314e9348f..f5217a0d93 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1481,6 +1481,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..5b0147ee2d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10902,6 +10902,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--S0GG+JvAI2G0KxBG
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v19-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 218 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 96b08d0500..9d617f95b9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25300,6 +25300,17 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -25400,6 +25411,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v16-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d9b3598977..fc1b4ac98c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2bd5f5ea14..1c77430f0c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1467,6 +1467,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4bce3ad8de..9f46cba5ed 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10904,6 +10904,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v17-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v18 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c06afd3ea..8ea064144f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25714,6 +25714,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 56420bbc9d..cc8e870740 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1468,6 +1468,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 219ac160f8..4824a55480 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,20 +568,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -606,13 +603,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -626,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -651,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -680,5 +699,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..5b0147ee2d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10902,6 +10902,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--5I6of5zJg18YgZEa
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v18-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v20 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..eead88419f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b6d35c2d11..360b6bda26 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1478,6 +1478,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 46d0977c2e..3324ffe16a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95604e988a..186ced4a35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10919,6 +10919,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--Z1Z8UV8BNhgCynIS
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v20-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v21 04/10] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 229 +++++++++++--------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 220 insertions(+), 91 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 959f6a1c2f..eead88419f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25741,6 +25741,27 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type>] </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>name</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 8625cbeab6..b9b2a6aa20 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1478,6 +1478,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9f4927220b..24d858d1bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -449,6 +464,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -476,79 +496,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -561,7 +511,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -571,7 +523,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -580,6 +532,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -594,8 +572,20 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ }
+ else
+ {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -614,20 +604,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -642,13 +639,34 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -662,14 +680,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -687,7 +705,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -716,5 +735,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 95604e988a..83927b510a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10919,6 +10919,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '9979', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '9980', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..20bfe71822 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..68e2bc6586 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--Tcb1KvpfnM4LxW2s
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v21-0005-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v14 3/8] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Change to use lstat() to allow pg_ls_dir_recurse to avoid infinite recursion.
That means:
- links to dirs are shown with isdir=false;
- timestamps shown are those of the link;
- changed pg_stat_file for consistency;
Need catversion bumped?
---
doc/src/sgml/func.sgml | 19 +-
src/backend/catalog/system_views.sql | 1 +
src/backend/utils/adt/genfile.c | 244 ++++++++++++-------
src/include/catalog/pg_proc.dat | 12 +
src/test/regress/expected/misc_functions.out | 24 ++
src/test/regress/input/tablespace.source | 5 +
src/test/regress/output/tablespace.source | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
8 files changed, 230 insertions(+), 94 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2c6142a0e0..68c7327e1d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21342,6 +21342,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
List the contents of a directory. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_ls_dir_metadata(<parameter>dirname</parameter> <type>text</type> [, <parameter>missing_ok</parameter> <type>boolean</type>, <parameter>include_dot_dirs</parameter> <type>boolean</type>])</function></literal>
+ </entry>
+ <entry><type>setof text</type></entry>
+ <entry>
+ For each file in a directory, list the file and its metadata. Restricted to superusers by default, but other users can be granted EXECUTE to run the function.
+ </entry>
+ </row>
<row>
<entry>
<literal><function>pg_ls_logdir()</function></literal>
@@ -21442,6 +21451,14 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
empty directory from an non-existent directory.
</para>
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <para>
+ <function>pg_ls_dir_metadata</function> lists the files in the specified
+ directory along with the file's metadata.
+ </para>
+
<indexterm>
<primary>pg_ls_logdir</primary>
</indexterm>
@@ -21528,7 +21545,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
size, last accessed time stamp, last modified time stamp,
last file status change time stamp (Unix platforms only),
file creation time stamp (Windows only), and a <type>boolean</type>
- indicating if it is a directory (or a symbolic link to a directory).
+ indicating if it is a directory (and not a symbolic link to a directory).
Typical usages include:
<programlisting>
SELECT * FROM pg_stat_file('filename');
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46912..05a644a7c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1435,6 +1435,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
--
-- We also set up some things as accessible to standard roles.
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 01185f218b..daae06aa9a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,6 +36,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything begining with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata.
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS|LS_DIR_SKIP_HIDDEN|LS_DIR_SKIP_SPECIAL|LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -370,7 +385,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
filename = convert_and_check_filename(filename_t);
- if (stat(filename, &fst) < 0)
+ if (lstat(filename, &fst) < 0)
{
if (missing_ok && errno == ENOENT)
PG_RETURN_NULL();
@@ -413,6 +428,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -440,79 +460,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- bool randomAccess;
- TupleDesc tupdesc;
- Tuplestorestate *tupstore;
- DIR *dirdesc;
- struct dirent *de;
- MemoryContext oldcontext;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* check the optional arguments */
- if (PG_NARGS() == 3)
- {
- 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")));
-
- /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
- oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
-
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0);
-
- randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo->returnMode = SFRM_Materialize;
- rsinfo->setResult = tupstore;
- rsinfo->setDesc = tupdesc;
-
- MemoryContextSwitchTo(oldcontext);
-
- dirdesc = AllocateDir(location);
- if (!dirdesc)
- {
- /* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
- return (Datum) 0;
- /* Otherwise, we can let ReadDir() throw the error */
- }
-
- 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;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(tupstore, tupdesc, values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -525,7 +475,9 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -535,7 +487,7 @@ pg_ls_dir_1arg(PG_FUNCTION_ARGS)
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
@@ -544,6 +496,32 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
+ TypeFuncClass tuptype ;
+
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
@@ -558,8 +536,18 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- elog(ERROR, "return type must be a row type");
+ tuptype = get_call_result_type(fcinfo, NULL, &tupdesc);
+ if (flags & LS_DIR_METADATA)
+ {
+ if (tuptype != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ } else {
+ /* pg_ls_dir returns a simple scalar */
+ if (tuptype != TYPEFUNC_SCALAR)
+ elog(ERROR, "return type must be a scalar type");
+ tupdesc = CreateTemplateTupleDesc(1);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "column", TEXTOID, -1, 0);
+ }
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
@@ -578,36 +566,77 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags&LS_DIR_MISSING_OK && errno == ENOENT)
+ {
+ tuplestore_donestoring(tupstore);
return (Datum) 0;
+ }
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
+ struct stat lattrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
- if (stat(path, &attrib) < 0)
+ if (stat(path, &attrib) < 0 ||
+ lstat(path, &lattrib) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", path)));
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(lattrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (S_ISDIR(attrib.st_mode))
+ {
+ /*
+ * Do nothing: links to dirs are not skipped (but are shown as
+ * isdir=false)
+ */
+ ;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) lattrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(lattrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+ values[3] = BoolGetDatum(S_ISDIR(lattrib.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+#endif
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
@@ -621,14 +650,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -646,7 +675,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -675,5 +705,33 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Function to return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f9dc..b1a957fe3e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10741,6 +10741,18 @@
proallargtypes => '{oid,text,int8,timestamptz}', proargmodes => '{i,o,o,o}',
proargnames => '{tablespace,name,size,modification}',
prosrc => 'pg_ls_tmpdir_1arg' },
+{ oid => '5032', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '5033', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,name,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 2e87c548eb..7930909f02 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -219,6 +219,30 @@ select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+ name | isdir
+------+-------
+ . | t
+(1 row)
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+ name | isdir
+------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ name | size | modification | isdir
+------+------+--------------+-------
+(0 rows)
+
--
-- Test adding a support function to a subject function
--
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a5f61a35dc..0b9cfe615e 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -11,6 +11,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 162b591b31..a42714bf40 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -13,6 +13,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index f6857ad177..372345720d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -65,6 +65,17 @@ select * from (select pg_ls_dir('.', false, false) as name) as ls where ls.name=
select pg_ls_dir('does not exist', true, false); -- ok with missingok=true
select pg_ls_dir('does not exist'); -- fails with missingok=false
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select name, isdir from pg_ls_dir_metadata('.') where name='.';
+
+select name, isdir from pg_ls_dir_metadata('.', false, false) where name='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test adding a support function to a subject function
--
--
2.17.0
--Kynn+LdAwU9N+JqL
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v14-0004-pg_ls_tmpdir-to-show-isdir-argument.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v37 03/11] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 205 ++++++++++++-------
src/include/catalog/pg_proc.dat | 12 ++
src/test/regress/expected/misc_functions.out | 24 +++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 217 insertions(+), 70 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d958c3e74ac..a80d794a43a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25900,6 +25900,27 @@ LOG: Grand total: 1651920 bytes in 201 blocks; 622360 free (88 chunks); 1029560
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 30a048f6b09..b77b83017cb 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -704,6 +704,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ab6f67f8747..c041c7630c8 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything beginning with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -516,6 +531,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -543,54 +563,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- DIR *dirdesc;
- struct dirent *de;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* 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);
- }
-
- InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
-
- 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(dirdesc, location)) != NULL)
- {
- Datum values[1];
- bool nulls[1];
-
- if (!include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -603,23 +578,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
DIR *dirdesc;
struct dirent *de;
- InitMaterializedSRF(fcinfo, 0);
+ /* isdir depends on metadata */
+ Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ /* missing_ok */
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ /* include_dot_dirs */
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
+
+ if (flags & LS_DIR_METADATA)
+ InitMaterializedSRF(fcinfo, 0);
+ else
+ InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
/*
* Now walk the directory. Note that we must do this within a single SRF
@@ -630,20 +637,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -658,13 +672,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+ else
+#endif
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -678,14 +714,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -703,7 +739,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -732,7 +769,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -741,7 +806,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -750,7 +815,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -775,5 +840,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 20f5aa56eab..89892647808 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11758,6 +11758,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 77d285ecc85..a656f9ad07e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -539,6 +539,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
pg_tblspc/NNN
(1 row)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index d299f3d8949..f808555382d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -181,6 +181,17 @@ select * from pg_ls_tmpdir() limit 0;
select * from pg_ls_waldir() limit 0;
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
FROM pg_tablespace WHERE spcname = 'regress_tblspace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.25.1
--Pk/CTwBz1VvfPIDp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v37-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v36 3/7] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 205 ++++++++++++-------
src/include/catalog/pg_proc.dat | 12 ++
src/test/regress/expected/misc_functions.out | 24 +++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 217 insertions(+), 70 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 933118203f4..ba30faa8441 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25940,6 +25940,27 @@ SELECT collation for ('foo' COLLATE "de_DE");
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 73da687d5dd..3fa6f9af0a8 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 2bf52192567..c7322ed405a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything beginning with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- DIR *dirdesc;
- struct dirent *de;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* 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);
- }
-
- SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
- 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(dirdesc, location)) != NULL)
- {
- Datum values[1];
- bool nulls[1];
-
- if (!include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -533,23 +508,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
DIR *dirdesc;
struct dirent *de;
- SetSingleFuncCall(fcinfo, 0);
+ /* isdir depends on metadata */
+ Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ /* missing_ok */
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ /* include_dot_dirs */
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
+
+ if (flags & LS_DIR_METADATA)
+ SetSingleFuncCall(fcinfo, 0);
+ else
+ SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
/*
* Now walk the directory. Note that we must do this within a single SRF
@@ -560,20 +567,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -588,13 +602,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+ else
+#endif
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -633,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -662,7 +699,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -671,7 +736,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -680,7 +745,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -705,5 +770,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a331..0e5c977e69f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11815,6 +11815,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 45544469af5..e54e38f54ad 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -475,6 +475,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
pg_tblspc/NNN
(1 row)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 59d6e517503..fcb456f434d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -155,6 +155,17 @@ select * from pg_ls_tmpdir() limit 0;
select * from pg_ls_waldir() limit 0;
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
FROM pg_tablespace WHERE spcname = 'regress_tblspace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.17.1
--4ybNbZnZ8tziJ7D6
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v36-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v34 03/15] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 203 ++++++++++++-------
src/include/catalog/pg_proc.dat | 12 ++
src/test/regress/expected/misc_functions.out | 24 +++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 215 insertions(+), 70 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0be4743e3ed..c2ff924d021 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25976,6 +25976,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 81bac6f5812..b4d3609cce7 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1ed01620a1b..1878bac7eb3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything beginning with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- DIR *dirdesc;
- struct dirent *de;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* 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);
- }
-
- SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
- 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(dirdesc, location)) != NULL)
- {
- Datum values[1];
- bool nulls[1];
-
- if (!include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -533,23 +508,53 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
DIR *dirdesc;
struct dirent *de;
- SetSingleFuncCall(fcinfo, 0);
+ /* isdir depends on metadata */
+ Assert(!(flags&LS_DIR_ISDIR) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags&LS_DIR_ISDIR) || !(flags&LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
+
+ if (flags & LS_DIR_METADATA)
+ SetSingleFuncCall(fcinfo, 0);
+ else
+ SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
/*
* Now walk the directory. Note that we must do this within a single SRF
@@ -560,20 +565,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -588,13 +600,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+ else
+#endif
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +642,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -633,7 +667,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -662,7 +697,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -671,7 +734,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -680,7 +743,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -705,5 +768,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1c..43ee0cdc454 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11677,6 +11677,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index b08e7c4a6d0..d18f401248a 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -452,6 +452,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 2dfbcfdebe1..c6b9074ab02 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,14 @@ SELECT spcoptions FROM pg_tablespace WHERE spcname = 'regress_tblspacewith';
DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ebb0352ac68..05794e1ae45 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -147,6 +147,17 @@ select * from pg_ls_tmpdir() limit 0;
select * from pg_ls_waldir() limit 0;
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 896f05cea32..3aadac8b611 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -23,6 +23,11 @@ DROP TABLESPACE regress_tblspacewith;
-- create a tablespace we can use
CREATE TABLESPACE regress_tblspace LOCATION '';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.17.1
--smOfPzt+Qjm5bNGJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v34-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* [PATCH v35 3/7] Add pg_ls_dir_metadata to list a dir with file metadata..
@ 2020-03-10 03:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Justin Pryzby @ 2020-03-10 03:40 UTC (permalink / raw)
Generalize pg_ls_dir_files and retire pg_ls_dir
Need catversion bumped?
---
doc/src/sgml/func.sgml | 21 ++
src/backend/catalog/system_functions.sql | 1 +
src/backend/utils/adt/genfile.c | 205 ++++++++++++-------
src/include/catalog/pg_proc.dat | 12 ++
src/test/regress/expected/misc_functions.out | 24 +++
src/test/regress/expected/tablespace.out | 8 +
src/test/regress/sql/misc_functions.sql | 11 +
src/test/regress/sql/tablespace.sql | 5 +
8 files changed, 217 insertions(+), 70 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d01aeec9f88..d2a455a3e27 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25987,6 +25987,27 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_ls_dir_metadata</primary>
+ </indexterm>
+ <function>pg_ls_dir_metadata</function> ( <parameter>dirname</parameter> <type>text</type>
+ <optional>, <parameter>missing_ok</parameter> <type>boolean</type>,
+ <parameter>include_dot_dirs</parameter> <type>boolean</type> </optional> )
+ <returnvalue>setof record</returnvalue>
+ ( <parameter>filename</parameter> <type>text</type>,
+ <parameter>size</parameter> <type>bigint</type>,
+ <parameter>modification</parameter> <type>timestamp with time zone</type> )
+ </para>
+ <para>
+ For each file in the specified directory, list the file and its
+ metadata.
+ Restricted to superusers by default, but other users can be granted
+ EXECUTE to run the function.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 81bac6f5812..b4d3609cce7 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -700,6 +700,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_file(text,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text) FROM public;
REVOKE EXECUTE ON FUNCTION pg_ls_dir(text,boolean,boolean) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_ls_dir_metadata(text,boolean,boolean) FROM public;
REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer) FROM PUBLIC;
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 88f279d1b31..75b7bf99849 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,6 +37,21 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags);
+
+#define LS_DIR_ISDIR (1<<0) /* Show column: isdir */
+#define LS_DIR_METADATA (1<<1) /* Show columns: mtime, size */
+#define LS_DIR_MISSING_OK (1<<2) /* Ignore ENOENT if the toplevel dir is missing */
+#define LS_DIR_SKIP_DOT_DIRS (1<<3) /* Do not show . or .. */
+#define LS_DIR_SKIP_HIDDEN (1<<4) /* Do not show anything beginning with . */
+#define LS_DIR_SKIP_DIRS (1<<5) /* Do not show directories */
+#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
+
+/*
+ * Shortcut for the historic behavior of the pg_ls_* functions (not including
+ * pg_ls_dir, which skips different files and doesn't show metadata).
+ */
+#define LS_DIR_HISTORIC (LS_DIR_SKIP_DIRS | LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -446,6 +461,11 @@ pg_stat_file(PG_FUNCTION_ARGS)
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime));
#endif
values[5] = BoolGetDatum(S_ISDIR(fst.st_mode));
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(filename))
+ values[5] = BoolGetDatum(false);
+#endif
tuple = heap_form_tuple(tupdesc, values, isnull);
@@ -473,54 +493,9 @@ pg_stat_file_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_dir(PG_FUNCTION_ARGS)
{
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
- char *location;
- bool missing_ok = false;
- bool include_dot_dirs = false;
- DIR *dirdesc;
- struct dirent *de;
-
- location = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
-
- /* 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);
- }
-
- SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
-
- 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(dirdesc, location)) != NULL)
- {
- Datum values[1];
- bool nulls[1];
-
- if (!include_dot_dirs &&
- (strcmp(de->d_name, ".") == 0 ||
- strcmp(de->d_name, "..") == 0))
- continue;
-
- values[0] = CStringGetTextDatum(de->d_name);
- nulls[0] = false;
-
- tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
- values, nulls);
- }
-
- FreeDir(dirdesc);
- return (Datum) 0;
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
@@ -533,23 +508,55 @@ pg_ls_dir(PG_FUNCTION_ARGS)
Datum
pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
- return pg_ls_dir(fcinfo);
+ text *filename_t = PG_GETARG_TEXT_PP(0);
+ char *filename = convert_and_check_filename(filename_t);
+ return pg_ls_dir_files(fcinfo, filename, LS_DIR_SKIP_DOT_DIRS);
}
/*
- * Generic function to return a directory listing of files.
+ * Generic function to return a directory listing of files (and optionally dirs).
*
- * If the directory isn't there, silently return an empty set if missing_ok.
+ * If the directory isn't there, silently return an empty set if MISSING_OK.
* Other unreadable-directory cases throw an error.
*/
static Datum
-pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
+pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
DIR *dirdesc;
struct dirent *de;
- SetSingleFuncCall(fcinfo, 0);
+ /* isdir depends on metadata */
+ Assert(!(flags & LS_DIR_ISDIR) || (flags & LS_DIR_METADATA));
+ /* Unreasonable to show isdir and skip dirs */
+ Assert(!(flags & LS_DIR_ISDIR) || !(flags & LS_DIR_SKIP_DIRS));
+
+ /* check the optional arguments */
+ if (PG_NARGS() == 3)
+ {
+ /* missing_ok */
+ if (!PG_ARGISNULL(1))
+ {
+ if (PG_GETARG_BOOL(1))
+ flags |= LS_DIR_MISSING_OK;
+ else
+ flags &= ~LS_DIR_MISSING_OK;
+ }
+
+ /* include_dot_dirs */
+ if (!PG_ARGISNULL(2))
+ {
+ if (PG_GETARG_BOOL(2))
+ flags &= ~LS_DIR_SKIP_DOT_DIRS;
+ else
+ flags |= LS_DIR_SKIP_DOT_DIRS;
+ }
+ }
+
+ if (flags & LS_DIR_METADATA)
+ SetSingleFuncCall(fcinfo, 0);
+ else
+ SetSingleFuncCall(fcinfo, SRF_SINGLE_USE_EXPECTED);
/*
* Now walk the directory. Note that we must do this within a single SRF
@@ -560,20 +567,27 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
if (!dirdesc)
{
/* Return empty tuplestore if appropriate */
- if (missing_ok && errno == ENOENT)
+ if (flags & LS_DIR_MISSING_OK && errno == ENOENT)
return (Datum) 0;
/* Otherwise, we can let ReadDir() throw the error */
}
while ((de = ReadDir(dirdesc, dir)) != NULL)
{
- Datum values[3];
- bool nulls[3];
+ Datum values[4];
+ bool nulls[4];
char path[MAXPGPATH * 2];
struct stat attrib;
- /* Skip hidden files */
- if (de->d_name[0] == '.')
+ /* Skip dot dirs? */
+ if (flags & LS_DIR_SKIP_DOT_DIRS &&
+ (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0))
+ continue;
+
+ /* Skip hidden files? */
+ if (flags & LS_DIR_SKIP_HIDDEN &&
+ de->d_name[0] == '.')
continue;
/* Get the file info */
@@ -588,13 +602,35 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
errmsg("could not stat file \"%s\": %m", path)));
}
- /* Ignore anything but regular files */
- if (!S_ISREG(attrib.st_mode))
- continue;
+ /* Skip dirs or special files? */
+ if (S_ISDIR(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_DIRS)
+ continue;
+ }
+ else if (!S_ISREG(attrib.st_mode))
+ {
+ if (flags & LS_DIR_SKIP_SPECIAL)
+ continue;
+ }
values[0] = CStringGetTextDatum(de->d_name);
- values[1] = Int64GetDatum((int64) attrib.st_size);
- values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_METADATA)
+ {
+ values[1] = Int64GetDatum((int64) attrib.st_size);
+ values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime));
+ if (flags & LS_DIR_ISDIR)
+ {
+#ifdef WIN32
+ /* Links should have isdir=false */
+ if (pgwin32_is_junction(path))
+ values[3] = BoolGetDatum(false);
+ else
+#endif
+ values[3] = BoolGetDatum(S_ISDIR(attrib.st_mode));
+ }
+ }
+
memset(nulls, 0, sizeof(nulls));
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
@@ -608,14 +644,14 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok)
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, Log_directory, false);
+ return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_HISTORIC);
}
/* Function to return the list of files in the WAL directory */
Datum
pg_ls_waldir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR, false);
+ return pg_ls_dir_files(fcinfo, XLOGDIR, LS_DIR_HISTORIC);
}
/*
@@ -633,7 +669,8 @@ pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc)
tblspc)));
TempTablespacePath(path, tblspc);
- return pg_ls_dir_files(fcinfo, path, true);
+ return pg_ls_dir_files(fcinfo, path,
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
}
/*
@@ -662,7 +699,35 @@ pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
Datum
pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
+ return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status",
+ LS_DIR_HISTORIC | LS_DIR_MISSING_OK);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ */
+Datum
+pg_ls_dir_metadata(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
+}
+
+/*
+ * Return the list of files and metadata in an arbitrary directory.
+ * note: this wrapper is necessary to pass the sanity check in opr_sanity,
+ * which checks that all built-in functions that share the implementing C
+ * function take the same number of arguments.
+ */
+Datum
+pg_ls_dir_metadata_1arg(PG_FUNCTION_ARGS)
+{
+ char *dirname = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
+
+ return pg_ls_dir_files(fcinfo, dirname,
+ LS_DIR_METADATA | LS_DIR_SKIP_SPECIAL | LS_DIR_ISDIR);
}
/*
@@ -671,7 +736,7 @@ pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", LS_DIR_HISTORIC);
}
/*
@@ -680,7 +745,7 @@ pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
Datum
pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
- return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
+ return pg_ls_dir_files(fcinfo, "pg_logical/mappings", LS_DIR_HISTORIC);
}
/*
@@ -705,5 +770,5 @@ pg_ls_replslotdir(PG_FUNCTION_ARGS)
slotname)));
snprintf(path, sizeof(path), "pg_replslot/%s", slotname);
- return pg_ls_dir_files(fcinfo, path, false);
+ return pg_ls_dir_files(fcinfo, path, LS_DIR_HISTORIC);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 25304430f44..7307f44f371 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -11769,6 +11769,18 @@
proargmodes => '{i,o,o,o}',
proargnames => '{slot_name,name,size,modification}',
prosrc => 'pg_ls_replslotdir' },
+{ oid => '8450', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text bool bool',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata' },
+{ oid => '8451', descr => 'list directory with metadata',
+ proname => 'pg_ls_dir_metadata', procost => '10', prorows => '20', proretset => 't',
+ provolatile => 'v', prorettype => 'record', proargtypes => 'text',
+ proallargtypes => '{text,text,int8,timestamptz,bool}', proargmodes => '{i,o,o,o,o}',
+ proargnames => '{dirname,filename,size,modification,isdir}',
+ prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
{ oid => '5028', descr => 'hash partition CHECK constraint',
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 45544469af5..e54e38f54ad 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -475,6 +475,30 @@ select * from pg_stat_file('.') limit 0;
------+--------+--------------+--------+----------+-------
(0 rows)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+ filename | isdir
+----------+-------
+ . | t
+(1 row)
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | isdir
+----------+-------
+(0 rows)
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+ filename | size | modification | isdir
+----------+------+--------------+-------
+(0 rows)
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index c52cf1cfcf9..8159c9f18f1 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -33,6 +33,14 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
pg_tblspc/NNN
(1 row)
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+ name | size | modification
+------+------+--------------
+(0 rows)
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 59d6e517503..fcb456f434d 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -155,6 +155,17 @@ select * from pg_ls_tmpdir() limit 0;
select * from pg_ls_waldir() limit 0;
select * from pg_stat_file('.') limit 0;
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+select * from pg_ls_tmpdir() where name='Does not exist';
+
+select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
+
+select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+
+-- Check that expected columns are present
+select * from pg_ls_dir_metadata('.') limit 0;
+
--
-- Test replication slot directory functions
--
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 21db433f2a8..cf683c3bf3a 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -27,6 +27,11 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN')
FROM pg_tablespace WHERE spcname = 'regress_tblspace';
+-- This tests the missing_ok parameter, which causes pg_ls_tmpdir to succeed even if the tmpdir doesn't exist yet
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+-- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
+SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
+
-- try setting and resetting some properties for the new tablespace
ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail
--
2.17.1
--olLTNZSltDMg5Vbm
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v35-0004-pg_ls_tmpdir-to-show-directories-and-isdir-argum.patch"
^ permalink raw reply [nested|flat] 68+ messages in thread
* Pre-proposal: unicode normalized text
@ 2023-09-12 22:47 Jeff Davis <[email protected]>
0 siblings, 1 reply; 68+ messages in thread
From: Jeff Davis @ 2023-09-12 22:47 UTC (permalink / raw)
To: pgsql-hackers
One of the frustrations with using the "C" locale (or any deterministic
locale) is that the following returns false:
SELECT 'á' = 'á'; -- false
because those are the unicode sequences U&'\0061\0301' and U&'\00E1',
respectively, so memcmp() returns non-zero. But it's really the same
character with just a different representation, and if you normalize
them they are equal:
SELECT normalize('á') = normalize('á'); -- true
The idea is to have a new data type, say "UTEXT", that normalizes the
input so that it can have an improved notion of equality while still
using memcmp().
Unicode guarantees that "the results of normalizing a string on one
version will always be the same as normalizing it on any other version,
as long as the string contains only assigned characters according to
both versions"[1]. It also guarantees that it "will not reallocate,
remove, or reassign" characters[2]. That means that we can normalize in
a forward-compatible way as long as we don't allow the use of
unassigned code points.
I looked at the standard to see what it had to say, and is discusses
normalization, but a standard UCS string with an unassigned code point
is not an error. Without a data type to enforce the constraint that
there are no unassigned code points, we can't guarantee forward
compatibility. Some other systems support NVARCHAR, but I didn't see
any guarantee of normalization or blocking unassigned code points
there, either.
UTEXT benefits:
* slightly better natural language semantics than TEXT with
deterministic collation
* still deterministic=true
* fast memcmp()-based comparisons
* no breaking semantic changes as unicode evolves
TEXT allows unassigned code points, and generally returns the same byte
sequences that were orgiinally entered; therefore UTEXT is not a
replacement for TEXT.
UTEXT could be built-in or it could be an extension or in contrib. If
an extension, we'd probably want to at least expose a function that can
detect unassigned code points, so that it's easy to be consistent with
the auto-generated unicode tables. I also notice that there already is
an unassigned code points table in saslprep.c, but it seems to be
frozen as of Unicode 3.2, and I'm not sure why.
Questions:
* Would this be useful enough to justify a new data type? Would it be
confusing about when to choose one versus the other?
* Would cross-type comparisons between TEXT and UTEXT become a major
problem that would reduce the utility?
* Should "some_utext_value = some_text_value" coerce the LHS to TEXT
or the RHS to UTEXT?
* Other comments or am I missing something?
Regards,
Jeff Davis
[1] https://unicode.org/reports/tr15/
[2] https://www.unicode.org/policies/stability_policy.html
^ permalink raw reply [nested|flat] 68+ messages in thread
* Re: Pre-proposal: unicode normalized text
@ 2023-10-02 20:27 Nico Williams <[email protected]>
parent: Jeff Davis <[email protected]>
0 siblings, 1 reply; 68+ messages in thread
From: Nico Williams @ 2023-10-02 20:27 UTC (permalink / raw)
To: Jeff Davis <[email protected]>; +Cc: pgsql-hackers
On Tue, Sep 12, 2023 at 03:47:10PM -0700, Jeff Davis wrote:
> One of the frustrations with using the "C" locale (or any deterministic
> locale) is that the following returns false:
>
> SELECT 'á' = 'á'; -- false
>
> because those are the unicode sequences U&'\0061\0301' and U&'\00E1',
> respectively, so memcmp() returns non-zero. But it's really the same
> character with just a different representation, and if you normalize
> them they are equal:
>
> SELECT normalize('á') = normalize('á'); -- true
I think you misunderstand Unicode normalization and equivalence. There
is no standard Unicode `normalize()` that would cause the above equality
predicate to be true. If you normalize to NFD (normal form decomposed)
then a _prefix_ of those two strings will be equal, but that's clearly
not what you're looking for.
PostgreSQL already has Unicode normalization support, though it would be
nice to also have form-insensitive indexing and equality predicates.
There are two ways to write 'á' in Unicode: one is pre-composed (one
codepoint) and the other is decomposed (two codepoints in this specific
case), and it would be nice to be able to preserve input form when
storing strings but then still be able to index and match them
form-insensitively (in the case of 'á' both equivalent representations
should be considered equal, and for UNIQUE indexes they should be
considered the same).
You could also have functions that perform lossy normalization in the
sort of way that soundex does, such as first normalizing to NFD then
dropping all combining codepoints which then could allow 'á' to be eq to
'a'. But this would not be a Unicode normalization function.
Nico
--
^ permalink raw reply [nested|flat] 68+ messages in thread
* Re: Pre-proposal: unicode normalized text
@ 2023-10-03 19:15 Jeff Davis <[email protected]>
parent: Nico Williams <[email protected]>
0 siblings, 0 replies; 68+ messages in thread
From: Jeff Davis @ 2023-10-03 19:15 UTC (permalink / raw)
To: Nico Williams <[email protected]>; +Cc: pgsql-hackers
On Mon, 2023-10-02 at 15:27 -0500, Nico Williams wrote:
> I think you misunderstand Unicode normalization and equivalence.
> There
> is no standard Unicode `normalize()` that would cause the above
> equality
> predicate to be true. If you normalize to NFD (normal form
> decomposed)
> then a _prefix_ of those two strings will be equal, but that's
> clearly
> not what you're looking for.
From [1]:
"Unicode Normalization Forms are formally defined normalizations of
Unicode strings which make it possible to determine whether any two
Unicode strings are equivalent to each other. Depending on the
particular Unicode Normalization Form, that equivalence can either be a
canonical equivalence or a compatibility equivalence... A binary
comparison of the transformed strings will then determine equivalence."
NFC and NFD are based on Canonical Equivalence.
"Canonical equivalence is a fundamental equivalency between characters
or sequences of characters which represent the same abstract character,
and which when correctly displayed should always have the same visual
appearance and behavior."
Can you explain why NFC (the default form of normalization used by the
postgres normalize() function), followed by memcmp(), is not the right
thing to use to determine Canonical Equivalence?
Or are you saying that Canonical Equivalence is not a useful thing to
test?
What do you mean about the "prefix"?
In Postgres today:
SELECT normalize(U&'\0061\0301', nfc)::bytea; -- \xc3a1
SELECT normalize(U&'\00E1', nfc)::bytea; -- \xc3a1
SELECT normalize(U&'\0061\0301', nfd)::bytea; -- \x61cc81
SELECT normalize(U&'\00E1', nfd)::bytea; -- \x61cc81
which looks useful to me, but I assume you are saying that it doesn't
generalize well to other cases?
[1] https://unicode.org/reports/tr15/
> There are two ways to write 'á' in Unicode: one is pre-composed (one
> codepoint) and the other is decomposed (two codepoints in this
> specific
> case), and it would be nice to be able to preserve input form when
> storing strings but then still be able to index and match them
> form-insensitively (in the case of 'á' both equivalent
> representations
> should be considered equal, and for UNIQUE indexes they should be
> considered the same).
Sometimes preserving input differences is a good thing, other times
it's not, depending on the context. Almost any data type has some
aspects of the input that might not be preserved -- leading zeros in a
number, or whitespace in jsonb, etc.
If text is stored as normalized with NFC, it could be frustrating if
the retrieved string has a different binary representation than the
source data. But it could also be frustrating to look at two strings
made up of ordinary characters that look identical and for the database
to consider them unequal.
Regards,
Jeff Davis
^ permalink raw reply [nested|flat] 68+ messages in thread
end of thread, other threads:[~2023-10-03 19:15 UTC | newest]
Thread overview: 68+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-08 22:15 [PATCH v11 4/9] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-08 22:15 [PATCH v10 4/9] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-08 22:15 [PATCH v13 3/8] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-08 22:15 [PATCH v9 04/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-08 22:15 [PATCH v12 05/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v28 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v14 3/8] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v24 04/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v21 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v25 04/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v26 04/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v27 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v23 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v19 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v18 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v35 3/7] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v30 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v32 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v37 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v31 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v20 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v34 03/15] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v33 03/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v36 3/7] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v22 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v17 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2020-03-10 03:40 [PATCH v16 04/10] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
2023-09-12 22:47 Pre-proposal: unicode normalized text Jeff Davis <[email protected]>
2023-10-02 20:27 ` Re: Pre-proposal: unicode normalized text Nico Williams <[email protected]>
2023-10-03 19:15 ` Re: Pre-proposal: unicode normalized text Jeff Davis <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox