agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v10 4/9] Add pg_ls_dir_metadata to list a dir with file metadata..
110+ messages / 8 participants
[nested] [flat]
* [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; 110+ 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] 110+ messages in thread
* [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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ 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; 110+ 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] 110+ messages in thread
* Re: Popcount optimization using AVX512
@ 2024-04-01 01:17 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-01 01:17 UTC (permalink / raw)
To: Amonson, Paul D <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Alvaro Herrera <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, Mar 30, 2024 at 03:03:29PM -0500, Nathan Bossart wrote:
> My current plan is to add some new tests for
> pg_popcount() with many bytes, and then I'll give it a few more days for
> any additional feedback before committing.
Here is a v18 with a couple of new tests. Otherwise, it is the same as
v17.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-01 11:06 ` Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Alvaro Herrera @ 2024-04-01 11:06 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On 2024-Mar-31, Nathan Bossart wrote:
> +uint64
> +pg_popcount_avx512(const char *buf, int bytes)
> +{
> + uint64 popcnt;
> + __m512i accum = _mm512_setzero_si512();
> +
> + for (; bytes >= sizeof(__m512i); bytes -= sizeof(__m512i))
> + {
> + const __m512i val = _mm512_loadu_si512((const __m512i *) buf);
> + const __m512i cnt = _mm512_popcnt_epi64(val);
> +
> + accum = _mm512_add_epi64(accum, cnt);
> + buf += sizeof(__m512i);
> + }
> +
> + popcnt = _mm512_reduce_add_epi64(accum);
> + return popcnt + pg_popcount_fast(buf, bytes);
> +}
Hmm, doesn't this arrangement cause an extra function call to
pg_popcount_fast to be used here? Given the level of micro-optimization
being used by this code, I would have thought that you'd have tried to
avoid that. (At least, maybe avoid the call if bytes is 0, no?)
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"El Maquinismo fue proscrito so pena de cosquilleo hasta la muerte"
(Ijon Tichy en Viajes, Stanislaw Lem)
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
@ 2024-04-01 15:53 ` Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-01 15:53 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Mon, Apr 01, 2024 at 01:06:12PM +0200, Alvaro Herrera wrote:
> On 2024-Mar-31, Nathan Bossart wrote:
>> + popcnt = _mm512_reduce_add_epi64(accum);
>> + return popcnt + pg_popcount_fast(buf, bytes);
>
> Hmm, doesn't this arrangement cause an extra function call to
> pg_popcount_fast to be used here? Given the level of micro-optimization
> being used by this code, I would have thought that you'd have tried to
> avoid that. (At least, maybe avoid the call if bytes is 0, no?)
Yes, it does. I did another benchmark on very small arrays and can see the
overhead. This is the time in milliseconds to run pg_popcount() on an
array 1 billion times:
size (bytes) HEAD AVX512-POPCNT
1 1707.685 3480.424
2 1926.694 4606.182
4 3210.412 5284.506
8 1920.703 3640.968
16 2936.91 4045.586
32 3627.956 5538.418
64 5347.213 3748.212
I suspect that anything below 64 bytes will see this regression, as that is
the earliest point where there are enough bytes for ZMM registers.
We could avoid the call if there are no remaining bytes, but the numbers
for the smallest arrays probably wouldn't improve much, and that might
actually add some overhead due to branching. The other option to avoid
this overhead is to put most of pg_bitutils.c into its header file so that
we can inline the call.
Reviewing the current callers of pg_popcount(), IIUC the only ones that are
passing very small arrays are the bit_count() implementations and a call in
the syslogger for a single byte. I don't know how much to worry about the
overhead for bit_count() since there's presumably a bunch of other
overhead, and the syslogger one could probably be fixed via an inline
function that pulled the value from pg_number_of_ones (which would probably
be an improvement over the status quo, anyway). But this is all to save a
couple of nanoseconds...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-01 21:11 ` Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Ants Aasma @ 2024-04-01 21:11 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Mon, 1 Apr 2024 at 18:53, Nathan Bossart <[email protected]> wrote:
>
> On Mon, Apr 01, 2024 at 01:06:12PM +0200, Alvaro Herrera wrote:
> > On 2024-Mar-31, Nathan Bossart wrote:
> >> + popcnt = _mm512_reduce_add_epi64(accum);
> >> + return popcnt + pg_popcount_fast(buf, bytes);
> >
> > Hmm, doesn't this arrangement cause an extra function call to
> > pg_popcount_fast to be used here? Given the level of micro-optimization
> > being used by this code, I would have thought that you'd have tried to
> > avoid that. (At least, maybe avoid the call if bytes is 0, no?)
>
> Yes, it does. I did another benchmark on very small arrays and can see the
> overhead. This is the time in milliseconds to run pg_popcount() on an
> array 1 billion times:
>
> size (bytes) HEAD AVX512-POPCNT
> 1 1707.685 3480.424
> 2 1926.694 4606.182
> 4 3210.412 5284.506
> 8 1920.703 3640.968
> 16 2936.91 4045.586
> 32 3627.956 5538.418
> 64 5347.213 3748.212
>
> I suspect that anything below 64 bytes will see this regression, as that is
> the earliest point where there are enough bytes for ZMM registers.
What about using the masking capabilities of AVX-512 to handle the
tail in the same code path? Masked out portions of a load instruction
will not generate an exception. To allow byte level granularity
masking, -mavx512bw is needed. Based on wikipedia this will only
disable this fast path on Knights Mill (Xeon Phi), in all other cases
VPOPCNTQ implies availability of BW.
Attached is an example of what I mean. I did not have a machine to
test it with, but the code generated looks sane. I added the clang
pragma because it insisted on unrolling otherwise and based on how the
instruction dependencies look that is probably not too helpful even
for large cases (needs to be tested). The configure check and compile
flags of course need to be amended for BW.
Regards,
Ants Aasma
Attachments:
[text/x-patch] avx512-popcnt-masked-tail.patch (1.2K, ../../CANwKhkM-YZGE527y00LPU1660GW6zicvX7a=ZOsxnYYU0hng3g@mail.gmail.com/2-avx512-popcnt-masked-tail.patch)
download | inline diff:
diff --git a/src/port/pg_popcount_avx512.c b/src/port/pg_popcount_avx512.c
index f86558d1ee5..7fb2ada16c9 100644
--- a/src/port/pg_popcount_avx512.c
+++ b/src/port/pg_popcount_avx512.c
@@ -30,20 +30,27 @@
uint64
pg_popcount_avx512(const char *buf, int bytes)
{
- uint64 popcnt;
+ __m512i val, cnt;
+ __mmask64 remaining_mask;
__m512i accum = _mm512_setzero_si512();
- for (; bytes >= sizeof(__m512i); bytes -= sizeof(__m512i))
+ #pragma clang loop unroll(disable)
+ for (; bytes > sizeof(__m512i); bytes -= sizeof(__m512i))
{
- const __m512i val = _mm512_loadu_si512((const __m512i *) buf);
- const __m512i cnt = _mm512_popcnt_epi64(val);
+ val = _mm512_loadu_si512((const __m512i *) buf);
+ cnt = _mm512_popcnt_epi64(val);
accum = _mm512_add_epi64(accum, cnt);
buf += sizeof(__m512i);
}
- popcnt = _mm512_reduce_add_epi64(accum);
- return popcnt + pg_popcount_fast(buf, bytes);
+ remaining_mask = ~0ULL >> (sizeof(__m512i) - bytes);
+ val = _mm512_maskz_loadu_epi8(remaining_mask, (const __m512i *) buf);
+ cnt = _mm512_popcnt_epi64(val);
+
+ accum = _mm512_add_epi64(accum, cnt);
+
+ return _mm512_reduce_add_epi64(accum);
}
#endif /* TRY_POPCNT_FAST */
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
@ 2024-04-01 21:31 ` Nathan Bossart <[email protected]>
2024-04-01 22:09 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
0 siblings, 3 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-01 21:31 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 12:11:59AM +0300, Ants Aasma wrote:
> What about using the masking capabilities of AVX-512 to handle the
> tail in the same code path? Masked out portions of a load instruction
> will not generate an exception. To allow byte level granularity
> masking, -mavx512bw is needed. Based on wikipedia this will only
> disable this fast path on Knights Mill (Xeon Phi), in all other cases
> VPOPCNTQ implies availability of BW.
Sounds promising. IMHO we should really be sure that these kinds of loads
won't generate segfaults and the like due to the masked-out portions. I
searched around a little bit but haven't found anything that seemed
definitive.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-01 22:09 ` Ants Aasma <[email protected]>
2024-04-01 22:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2 siblings, 1 reply; 110+ messages in thread
From: Ants Aasma @ 2024-04-01 22:09 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, 2 Apr 2024 at 00:31, Nathan Bossart <[email protected]> wrote:
>
> On Tue, Apr 02, 2024 at 12:11:59AM +0300, Ants Aasma wrote:
> > What about using the masking capabilities of AVX-512 to handle the
> > tail in the same code path? Masked out portions of a load instruction
> > will not generate an exception. To allow byte level granularity
> > masking, -mavx512bw is needed. Based on wikipedia this will only
> > disable this fast path on Knights Mill (Xeon Phi), in all other cases
> > VPOPCNTQ implies availability of BW.
>
> Sounds promising. IMHO we should really be sure that these kinds of loads
> won't generate segfaults and the like due to the masked-out portions. I
> searched around a little bit but haven't found anything that seemed
> definitive.
Interestingly the Intel software developer manual is not exactly
crystal clear on how memory faults with masks work, but volume 2A
chapter 2.8 [1] does specify that MOVDQU8 is of exception class E4.nb
that supports memory fault suppression on page fault.
Regards,
Ants Aasma
[1] https://cdrdv2-public.intel.com/819712/253666-sdm-vol-2a.pdf
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:09 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
@ 2024-04-01 22:15 ` Nathan Bossart <[email protected]>
0 siblings, 0 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-01 22:15 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 01:09:57AM +0300, Ants Aasma wrote:
> On Tue, 2 Apr 2024 at 00:31, Nathan Bossart <[email protected]> wrote:
>> On Tue, Apr 02, 2024 at 12:11:59AM +0300, Ants Aasma wrote:
>> > What about using the masking capabilities of AVX-512 to handle the
>> > tail in the same code path? Masked out portions of a load instruction
>> > will not generate an exception. To allow byte level granularity
>> > masking, -mavx512bw is needed. Based on wikipedia this will only
>> > disable this fast path on Knights Mill (Xeon Phi), in all other cases
>> > VPOPCNTQ implies availability of BW.
>>
>> Sounds promising. IMHO we should really be sure that these kinds of loads
>> won't generate segfaults and the like due to the masked-out portions. I
>> searched around a little bit but haven't found anything that seemed
>> definitive.
>
> Interestingly the Intel software developer manual is not exactly
> crystal clear on how memory faults with masks work, but volume 2A
> chapter 2.8 [1] does specify that MOVDQU8 is of exception class E4.nb
> that supports memory fault suppression on page fault.
Perhaps Paul or Akash could chime in here...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-01 22:11 ` Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-01 22:11 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
Here is a v19 of the patch set. I moved out the refactoring of the
function pointer selection code to 0001. I think this is a good change
independent of $SUBJECT, and I plan to commit this soon. In 0002, I
changed the syslogger.c usage of pg_popcount() to use pg_number_of_ones
instead. This is standard practice elsewhere where the popcount functions
are unlikely to win. I'll probably commit this one soon, too, as it's even
more trivial than 0001.
0003 is the AVX512 POPCNT patch. Besides refactoring out 0001, there are
no changes from v18. 0004 is an early proof-of-concept for using AVX512
for the visibility map code. The code is missing comments, and I haven't
performed any benchmarking yet, but I figured I'd post it because it
demonstrates how it's possible to build upon 0003 in other areas.
AFAICT the main open question is the function call overhead in 0003 that
Alvaro brought up earlier. After 0002 is committed, I believe the only
in-tree caller of pg_popcount() with very few bytes is bit_count(), and I'm
not sure it's worth expending too much energy to make sure there are
absolutely no regressions there. However, I'm happy to do so if folks feel
that it is necessary, and I'd be grateful for thoughts on how to proceed on
this one.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-02 15:53 ` Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-02 15:53 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Mon, Apr 01, 2024 at 05:11:17PM -0500, Nathan Bossart wrote:
> Here is a v19 of the patch set. I moved out the refactoring of the
> function pointer selection code to 0001. I think this is a good change
> independent of $SUBJECT, and I plan to commit this soon. In 0002, I
> changed the syslogger.c usage of pg_popcount() to use pg_number_of_ones
> instead. This is standard practice elsewhere where the popcount functions
> are unlikely to win. I'll probably commit this one soon, too, as it's even
> more trivial than 0001.
>
> 0003 is the AVX512 POPCNT patch. Besides refactoring out 0001, there are
> no changes from v18. 0004 is an early proof-of-concept for using AVX512
> for the visibility map code. The code is missing comments, and I haven't
> performed any benchmarking yet, but I figured I'd post it because it
> demonstrates how it's possible to build upon 0003 in other areas.
I've committed the first two patches, and I've attached a rebased version
of the latter two.
> AFAICT the main open question is the function call overhead in 0003 that
> Alvaro brought up earlier. After 0002 is committed, I believe the only
> in-tree caller of pg_popcount() with very few bytes is bit_count(), and I'm
> not sure it's worth expending too much energy to make sure there are
> absolutely no regressions there. However, I'm happy to do so if folks feel
> that it is necessary, and I'd be grateful for thoughts on how to proceed on
> this one.
Another idea I had is to turn pg_popcount() into a macro that just uses the
pg_number_of_ones array when called for few bytes:
static inline uint64
pg_popcount_inline(const char *buf, int bytes)
{
uint64 popcnt = 0;
while (bytes--)
popcnt += pg_number_of_ones[(unsigned char) *buf++];
return popcnt;
}
#define pg_popcount(buf, bytes) \
((bytes < 64) ? \
pg_popcount_inline(buf, bytes) : \
pg_popcount_optimized(buf, bytes))
But again, I'm not sure this is really worth it for the current use-cases.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-02 17:34 ` Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Alvaro Herrera @ 2024-04-02 17:34 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On 2024-Apr-02, Nathan Bossart wrote:
> Another idea I had is to turn pg_popcount() into a macro that just uses the
> pg_number_of_ones array when called for few bytes:
>
> static inline uint64
> pg_popcount_inline(const char *buf, int bytes)
> {
> uint64 popcnt = 0;
>
> while (bytes--)
> popcnt += pg_number_of_ones[(unsigned char) *buf++];
>
> return popcnt;
> }
>
> #define pg_popcount(buf, bytes) \
> ((bytes < 64) ? \
> pg_popcount_inline(buf, bytes) : \
> pg_popcount_optimized(buf, bytes))
>
> But again, I'm not sure this is really worth it for the current use-cases.
Eh, that seems simple enough, and then you can forget about that case.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"No hay hombre que no aspire a la plenitud, es decir,
la suma de experiencias de que un hombre es capaz"
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
@ 2024-04-02 17:43 ` Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Tom Lane @ 2024-04-02 17:43 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
Alvaro Herrera <[email protected]> writes:
> On 2024-Apr-02, Nathan Bossart wrote:
>> Another idea I had is to turn pg_popcount() into a macro that just uses the
>> pg_number_of_ones array when called for few bytes:
>>
>> static inline uint64
>> pg_popcount_inline(const char *buf, int bytes)
>> {
>> uint64 popcnt = 0;
>>
>> while (bytes--)
>> popcnt += pg_number_of_ones[(unsigned char) *buf++];
>>
>> return popcnt;
>> }
>>
>> #define pg_popcount(buf, bytes) \
>> ((bytes < 64) ? \
>> pg_popcount_inline(buf, bytes) : \
>> pg_popcount_optimized(buf, bytes))
>>
>> But again, I'm not sure this is really worth it for the current use-cases.
> Eh, that seems simple enough, and then you can forget about that case.
I don't like the double evaluation of the macro argument. Seems like
you could get the same results more safely with
static inline uint64
pg_popcount(const char *buf, int bytes)
{
if (bytes < 64)
{
uint64 popcnt = 0;
while (bytes--)
popcnt += pg_number_of_ones[(unsigned char) *buf++];
return popcnt;
}
return pg_popcount_optimized(buf, bytes);
}
regards, tom lane
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
@ 2024-04-02 18:40 ` Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-02 18:40 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 01:43:48PM -0400, Tom Lane wrote:
> Alvaro Herrera <[email protected]> writes:
>> On 2024-Apr-02, Nathan Bossart wrote:
>>> Another idea I had is to turn pg_popcount() into a macro that just uses the
>>> pg_number_of_ones array when called for few bytes:
>>>
>>> static inline uint64
>>> pg_popcount_inline(const char *buf, int bytes)
>>> {
>>> uint64 popcnt = 0;
>>>
>>> while (bytes--)
>>> popcnt += pg_number_of_ones[(unsigned char) *buf++];
>>>
>>> return popcnt;
>>> }
>>>
>>> #define pg_popcount(buf, bytes) \
>>> ((bytes < 64) ? \
>>> pg_popcount_inline(buf, bytes) : \
>>> pg_popcount_optimized(buf, bytes))
>>>
>>> But again, I'm not sure this is really worth it for the current use-cases.
>
>> Eh, that seems simple enough, and then you can forget about that case.
>
> I don't like the double evaluation of the macro argument. Seems like
> you could get the same results more safely with
>
> static inline uint64
> pg_popcount(const char *buf, int bytes)
> {
> if (bytes < 64)
> {
> uint64 popcnt = 0;
>
> while (bytes--)
> popcnt += pg_number_of_ones[(unsigned char) *buf++];
>
> return popcnt;
> }
> return pg_popcount_optimized(buf, bytes);
> }
Yeah, I like that better. I'll do some testing to see what the threshold
really should be before posting an actual patch.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-02 22:01 ` Nathan Bossart <[email protected]>
2024-04-02 22:20 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-02 22:01 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 01:40:21PM -0500, Nathan Bossart wrote:
> On Tue, Apr 02, 2024 at 01:43:48PM -0400, Tom Lane wrote:
>> I don't like the double evaluation of the macro argument. Seems like
>> you could get the same results more safely with
>>
>> static inline uint64
>> pg_popcount(const char *buf, int bytes)
>> {
>> if (bytes < 64)
>> {
>> uint64 popcnt = 0;
>>
>> while (bytes--)
>> popcnt += pg_number_of_ones[(unsigned char) *buf++];
>>
>> return popcnt;
>> }
>> return pg_popcount_optimized(buf, bytes);
>> }
>
> Yeah, I like that better. I'll do some testing to see what the threshold
> really should be before posting an actual patch.
My testing shows that inlining wins with fewer than 8 bytes for the current
"fast" implementation. The "fast" implementation wins with fewer than 64
bytes compared to the AVX-512 implementation. These results are pretty
intuitive because those are the points at which the optimizations kick in.
In v21, 0001 is just the above inlining idea, which seems worth doing
independent of $SUBJECT. 0002 and 0003 are the AVX-512 patches, which I've
modified similarly to 0001, i.e., I've inlined the "fast" version in the
function pointer to avoid the function call overhead when there are fewer
than 64 bytes. All of this overhead juggling should result in choosing the
optimal popcount implementation depending on how many bytes there are to
process, roughly speaking.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-02 22:20 ` Nathan Bossart <[email protected]>
2024-04-03 02:09 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-02 22:20 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 05:01:32PM -0500, Nathan Bossart wrote:
> In v21, 0001 is just the above inlining idea, which seems worth doing
> independent of $SUBJECT. 0002 and 0003 are the AVX-512 patches, which I've
> modified similarly to 0001, i.e., I've inlined the "fast" version in the
> function pointer to avoid the function call overhead when there are fewer
> than 64 bytes. All of this overhead juggling should result in choosing the
> optimal popcount implementation depending on how many bytes there are to
> process, roughly speaking.
Sorry for the noise. I noticed a couple of silly mistakes immediately
after sending v21.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:20 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-03 02:09 ` Nathan Bossart <[email protected]>
2024-04-03 17:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-03 02:09 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 05:20:20PM -0500, Nathan Bossart wrote:
> Sorry for the noise. I noticed a couple of silly mistakes immediately
> after sending v21.
Sigh... I missed a line while rebasing these patches, which seems to have
grossly offended cfbot. Apologies again for the noise.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:20 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 02:09 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-03 17:41 ` Nathan Bossart <[email protected]>
2024-04-03 20:12 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-03 17:41 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
I committed v23-0001. Here is a rebased version of the remaining patches.
I intend to test the masking idea from Ants next.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:20 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 02:09 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 17:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-03 20:12 ` Nathan Bossart <[email protected]>
0 siblings, 0 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-03 20:12 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Ants Aasma <[email protected]>; Amonson, Paul D <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Wed, Apr 03, 2024 at 12:41:27PM -0500, Nathan Bossart wrote:
> I committed v23-0001. Here is a rebased version of the remaining patches.
> I intend to test the masking idea from Ants next.
0002 was missing a cast that is needed for the 32-bit builds. I've fixed
that in v25.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-02 20:30 ` Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2 siblings, 1 reply; 110+ messages in thread
From: Ants Aasma @ 2024-04-02 20:30 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, 2 Apr 2024 at 00:31, Nathan Bossart <[email protected]> wrote:
> On Tue, Apr 02, 2024 at 12:11:59AM +0300, Ants Aasma wrote:
> > What about using the masking capabilities of AVX-512 to handle the
> > tail in the same code path? Masked out portions of a load instruction
> > will not generate an exception. To allow byte level granularity
> > masking, -mavx512bw is needed. Based on wikipedia this will only
> > disable this fast path on Knights Mill (Xeon Phi), in all other cases
> > VPOPCNTQ implies availability of BW.
>
> Sounds promising. IMHO we should really be sure that these kinds of loads
> won't generate segfaults and the like due to the masked-out portions. I
> searched around a little bit but haven't found anything that seemed
> definitive.
After sleeping on the problem, I think we can avoid this question
altogether while making the code faster by using aligned accesses.
Loads that straddle cache line boundaries run internally as 2 load
operations. Gut feel says that there are enough out-of-order resources
available to make it not matter in most cases. But even so, not doing
the extra work is surely better. Attached is another approach that
does aligned accesses, and thereby avoids going outside bounds.
Would be interesting to see how well that fares in the small use case.
Anything that fits into one aligned cache line should be constant
speed, and there is only one branch, but the mask setup and folding
the separate popcounts together should add up to about 20-ish cycles
of overhead.
Regards,
Ants Aasma
Attachments:
[text/x-patch] avx512-popcnt-aligned-and-masked.patch (1.9K, ../../CANwKhkNs9WnqCZDV3bKgaqZix0_NC1T=wf77=k4jYgG=Qr-vzw@mail.gmail.com/2-avx512-popcnt-aligned-and-masked.patch)
download | inline diff:
diff --git a/src/port/pg_popcount_avx512.c b/src/port/pg_popcount_avx512.c
index f86558d1ee5..e1fbd98fa14 100644
--- a/src/port/pg_popcount_avx512.c
+++ b/src/port/pg_popcount_avx512.c
@@ -30,20 +30,44 @@
uint64
pg_popcount_avx512(const char *buf, int bytes)
{
- uint64 popcnt;
+ __m512i val, cnt;
__m512i accum = _mm512_setzero_si512();
+ const char *final;
+ int tail_idx;
+ __mmask64 mask = -1;
- for (; bytes >= sizeof(__m512i); bytes -= sizeof(__m512i))
- {
- const __m512i val = _mm512_loadu_si512((const __m512i *) buf);
- const __m512i cnt = _mm512_popcnt_epi64(val);
+ /*
+ * Align buffer down to avoid double load overhead from unaligned access.
+ * Calculate a mask to ignore preceding bytes. Find start offset of final
+ * iteration and number of valid bytes making sure that final iteration
+ * is not empty.
+ */
+ mask <<= ((uintptr_t) buf) % sizeof(__m512i);
+ tail_idx = (((uintptr_t) buf + bytes - 1) % sizeof(__m512i)) + 1;
+ final = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf + bytes - 1);
+ buf = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf);
+ /*
+ * Iterate through all but the final iteration. Starting from second
+ * iteration, the start index mask is ignored.
+ */
+ for (; buf < final; buf += sizeof(__m512i))
+ {
+ val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
+ cnt = _mm512_popcnt_epi64(val);
accum = _mm512_add_epi64(accum, cnt);
- buf += sizeof(__m512i);
+
+ mask = -1;
}
- popcnt = _mm512_reduce_add_epi64(accum);
- return popcnt + pg_popcount_fast(buf, bytes);
+ /* Final iteration needs to ignore bytes that are not within the length */
+ mask &= ((~0ULL) >> (64 - tail_idx));
+
+ val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
+ cnt = _mm512_popcnt_epi64(val);
+ accum = _mm512_add_epi64(accum, cnt);
+
+ return _mm512_reduce_add_epi64(accum);
}
#endif /* TRY_POPCNT_FAST */
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
@ 2024-04-03 22:50 ` Nathan Bossart <[email protected]>
2024-04-04 03:28 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
0 siblings, 2 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-03 22:50 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Tue, Apr 02, 2024 at 11:30:39PM +0300, Ants Aasma wrote:
> On Tue, 2 Apr 2024 at 00:31, Nathan Bossart <[email protected]> wrote:
>> On Tue, Apr 02, 2024 at 12:11:59AM +0300, Ants Aasma wrote:
>> > What about using the masking capabilities of AVX-512 to handle the
>> > tail in the same code path? Masked out portions of a load instruction
>> > will not generate an exception. To allow byte level granularity
>> > masking, -mavx512bw is needed. Based on wikipedia this will only
>> > disable this fast path on Knights Mill (Xeon Phi), in all other cases
>> > VPOPCNTQ implies availability of BW.
>>
>> Sounds promising. IMHO we should really be sure that these kinds of loads
>> won't generate segfaults and the like due to the masked-out portions. I
>> searched around a little bit but haven't found anything that seemed
>> definitive.
>
> After sleeping on the problem, I think we can avoid this question
> altogether while making the code faster by using aligned accesses.
> Loads that straddle cache line boundaries run internally as 2 load
> operations. Gut feel says that there are enough out-of-order resources
> available to make it not matter in most cases. But even so, not doing
> the extra work is surely better. Attached is another approach that
> does aligned accesses, and thereby avoids going outside bounds.
>
> Would be interesting to see how well that fares in the small use case.
> Anything that fits into one aligned cache line should be constant
> speed, and there is only one branch, but the mask setup and folding
> the separate popcounts together should add up to about 20-ish cycles
> of overhead.
I tested your patch in comparison to v25 and saw the following:
bytes v25 v25+ants
2 1108.205 1033.132
4 1311.227 1289.373
8 1927.954 2360.113
16 2281.091 2365.408
32 3856.992 2390.688
64 3648.72 3242.498
128 4108.549 3607.148
256 4910.076 4496.852
For 2 bytes and 4 bytes, the inlining should take effect, so any difference
there is likely just noise. At 8 bytes, we are calling the function
pointer, and there is a small regression with the masking approach.
However, by 16 bytes, the masking approach is on par with v25, and it wins
for all larger buffers, although the gains seem to taper off a bit.
If we can verify this approach won't cause segfaults and can stomach the
regression between 8 and 16 bytes, I'd happily pivot to this approach so
that we can avoid the function call dance that I have in v25.
Thoughts?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-04 03:28 ` David Rowley <[email protected]>
2024-04-04 17:18 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
1 sibling, 1 reply; 110+ messages in thread
From: David Rowley @ 2024-04-04 03:28 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, 4 Apr 2024 at 11:50, Nathan Bossart <[email protected]> wrote:
> If we can verify this approach won't cause segfaults and can stomach the
> regression between 8 and 16 bytes, I'd happily pivot to this approach so
> that we can avoid the function call dance that I have in v25.
>
> Thoughts?
If we're worried about regressions with some narrow range of byte
values, wouldn't it make more sense to compare that to cc4826dd5~1 at
the latest rather than to some version that's already probably faster
than PG16?
David
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 03:28 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
@ 2024-04-04 17:18 ` Nathan Bossart <[email protected]>
0 siblings, 0 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-04 17:18 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 04, 2024 at 04:28:58PM +1300, David Rowley wrote:
> On Thu, 4 Apr 2024 at 11:50, Nathan Bossart <[email protected]> wrote:
>> If we can verify this approach won't cause segfaults and can stomach the
>> regression between 8 and 16 bytes, I'd happily pivot to this approach so
>> that we can avoid the function call dance that I have in v25.
>
> If we're worried about regressions with some narrow range of byte
> values, wouldn't it make more sense to compare that to cc4826dd5~1 at
> the latest rather than to some version that's already probably faster
> than PG16?
Good point. When compared with REL_16_STABLE, Ants's idea still wins:
bytes v25 v25+ants REL_16_STABLE
2 1108.205 1033.132 2039.342
4 1311.227 1289.373 3207.217
8 1927.954 2360.113 3200.238
16 2281.091 2365.408 4457.769
32 3856.992 2390.688 6206.689
64 3648.72 3242.498 9619.403
128 4108.549 3607.148 17912.081
256 4910.076 4496.852 33591.385
As before, with 2 and 4 bytes, HEAD is using the inlined approach, but
REL_16_STABLE is doing a function call. For 8 bytes, REL_16_STABLE is
doing a function call as well as a call to a function pointer. At 16
bytes, it's doing a function call and two calls to a function pointer.
With Ant's approach, both 8 and 16 bytes require a single call to a
function pointer, and of course we are using the AVX-512 implementation for
both.
I think this is sufficient to justify switching approaches.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-04 13:02 ` Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
1 sibling, 1 reply; 110+ messages in thread
From: Ants Aasma @ 2024-04-04 13:02 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, 4 Apr 2024 at 01:50, Nathan Bossart <[email protected]> wrote:
> If we can verify this approach won't cause segfaults and can stomach the
> regression between 8 and 16 bytes, I'd happily pivot to this approach so
> that we can avoid the function call dance that I have in v25.
The approach I posted does not rely on masking performing page fault
suppression. All loads are 64 byte aligned and always contain at least
one byte of the buffer and therefore are guaranteed to be within a
valid page.
I personally don't mind it being slower for the very small cases,
because when performance on those sizes really matters it makes much
more sense to shoot for an inlined version instead.
Speaking of which, what does bumping up the inlined version threshold
to 16 do with and without AVX-512 available? Linearly extrapolating
the 2 and 4 byte numbers it might just come ahead in both cases,
making the choice easy.
Regards,
Ants Aasma
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
@ 2024-04-04 17:28 ` Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-04 17:28 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 04, 2024 at 04:02:53PM +0300, Ants Aasma wrote:
> Speaking of which, what does bumping up the inlined version threshold
> to 16 do with and without AVX-512 available? Linearly extrapolating
> the 2 and 4 byte numbers it might just come ahead in both cases,
> making the choice easy.
IIRC the inlined version starts losing pretty quickly after 8 bytes. As I
noted in my previous message, I think we have enough data to switch to your
approach already, so I think it's a moot point.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-05 04:15 ` Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-05 04:15 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
Here is an updated patch set. IMHO this is in decent shape and is
approaching committable.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-05 07:33 ` Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Ants Aasma @ 2024-04-05 07:33 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Fri, 5 Apr 2024 at 07:15, Nathan Bossart <[email protected]> wrote:
> Here is an updated patch set. IMHO this is in decent shape and is
> approaching committable.
I checked the code generation on various gcc and clang versions. It
looks mostly fine starting from versions where avx512 is supported,
gcc-7.1 and clang-5.
The main issue I saw was that clang was able to peel off the first
iteration of the loop and then eliminate the mask assignment and
replace masked load with a memory operand for vpopcnt. I was not able
to convince gcc to do that regardless of optimization options.
Generated code for the inner loop:
clang:
<L2>:
50: add rdx, 64
54: cmp rdx, rdi
57: jae <L1>
59: vpopcntq zmm1, zmmword ptr [rdx]
5f: vpaddq zmm0, zmm1, zmm0
65: jmp <L2>
gcc:
<L1>:
38: kmovq k1, rdx
3d: vmovdqu8 zmm0 {k1} {z}, zmmword ptr [rax]
43: add rax, 64
47: mov rdx, -1
4e: vpopcntq zmm0, zmm0
54: vpaddq zmm0, zmm0, zmm1
5a: vmovdqa64 zmm1, zmm0
60: cmp rax, rsi
63: jb <L1>
I'm not sure how much that matters in practice. Attached is a patch to
do this manually giving essentially the same result in gcc. As most
distro packages are built using gcc I think it would make sense to
have the extra code if it gives a noticeable benefit for large cases.
The visibility map patch has the same issue, otherwise looks good.
Regards,
Ants Aasma
Attachments:
[text/x-patch] avx512-peel-first-iteration.patch (915B, ../../CANwKhkMQtZCxa+nq=9QAoT6rgSQ48cVpH83tO3Md+-ck4bVz2w@mail.gmail.com/2-avx512-peel-first-iteration.patch)
download | inline diff:
diff --git a/src/port/pg_popcount_avx512.c b/src/port/pg_popcount_avx512.c
index dacc7553d29..f6e718b86e9 100644
--- a/src/port/pg_popcount_avx512.c
+++ b/src/port/pg_popcount_avx512.c
@@ -52,13 +52,21 @@ pg_popcount_avx512(const char *buf, int bytes)
* Iterate through all but the final iteration. Starting from second
* iteration, the start index mask is ignored.
*/
- for (; buf < final; buf += sizeof(__m512i))
+ if (buf < final)
{
val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
cnt = _mm512_popcnt_epi64(val);
accum = _mm512_add_epi64(accum, cnt);
+ buf += sizeof(__m512i);
mask = ~UINT64CONST(0);
+
+ for (; buf < final; buf += sizeof(__m512i))
+ {
+ val = _mm512_load_si512((const __m512i *) buf);
+ cnt = _mm512_popcnt_epi64(val);
+ accum = _mm512_add_epi64(accum, cnt);
+ }
}
/* Final iteration needs to ignore bytes that are not within the length */
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
@ 2024-04-05 12:58 ` Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-05 12:58 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Fri, Apr 05, 2024 at 10:33:27AM +0300, Ants Aasma wrote:
> The main issue I saw was that clang was able to peel off the first
> iteration of the loop and then eliminate the mask assignment and
> replace masked load with a memory operand for vpopcnt. I was not able
> to convince gcc to do that regardless of optimization options.
> Generated code for the inner loop:
>
> clang:
> <L2>:
> 50: add rdx, 64
> 54: cmp rdx, rdi
> 57: jae <L1>
> 59: vpopcntq zmm1, zmmword ptr [rdx]
> 5f: vpaddq zmm0, zmm1, zmm0
> 65: jmp <L2>
>
> gcc:
> <L1>:
> 38: kmovq k1, rdx
> 3d: vmovdqu8 zmm0 {k1} {z}, zmmword ptr [rax]
> 43: add rax, 64
> 47: mov rdx, -1
> 4e: vpopcntq zmm0, zmm0
> 54: vpaddq zmm0, zmm0, zmm1
> 5a: vmovdqa64 zmm1, zmm0
> 60: cmp rax, rsi
> 63: jb <L1>
>
> I'm not sure how much that matters in practice. Attached is a patch to
> do this manually giving essentially the same result in gcc. As most
> distro packages are built using gcc I think it would make sense to
> have the extra code if it gives a noticeable benefit for large cases.
Yeah, I did see this, but I also wasn't sure if it was worth further
complicating the code. I can test with and without your fix and see if it
makes any difference in the benchmarks.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-05 15:38 ` Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-05 15:38 UTC (permalink / raw)
To: Ants Aasma <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Fri, Apr 05, 2024 at 07:58:44AM -0500, Nathan Bossart wrote:
> On Fri, Apr 05, 2024 at 10:33:27AM +0300, Ants Aasma wrote:
>> The main issue I saw was that clang was able to peel off the first
>> iteration of the loop and then eliminate the mask assignment and
>> replace masked load with a memory operand for vpopcnt. I was not able
>> to convince gcc to do that regardless of optimization options.
>> Generated code for the inner loop:
>>
>> clang:
>> <L2>:
>> 50: add rdx, 64
>> 54: cmp rdx, rdi
>> 57: jae <L1>
>> 59: vpopcntq zmm1, zmmword ptr [rdx]
>> 5f: vpaddq zmm0, zmm1, zmm0
>> 65: jmp <L2>
>>
>> gcc:
>> <L1>:
>> 38: kmovq k1, rdx
>> 3d: vmovdqu8 zmm0 {k1} {z}, zmmword ptr [rax]
>> 43: add rax, 64
>> 47: mov rdx, -1
>> 4e: vpopcntq zmm0, zmm0
>> 54: vpaddq zmm0, zmm0, zmm1
>> 5a: vmovdqa64 zmm1, zmm0
>> 60: cmp rax, rsi
>> 63: jb <L1>
>>
>> I'm not sure how much that matters in practice. Attached is a patch to
>> do this manually giving essentially the same result in gcc. As most
>> distro packages are built using gcc I think it would make sense to
>> have the extra code if it gives a noticeable benefit for large cases.
>
> Yeah, I did see this, but I also wasn't sure if it was worth further
> complicating the code. I can test with and without your fix and see if it
> makes any difference in the benchmarks.
This seems to provide a small performance boost, so I've incorporated it
into v27.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-05 23:08 ` David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: David Rowley @ 2024-04-05 23:08 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, 6 Apr 2024 at 04:38, Nathan Bossart <[email protected]> wrote:
> This seems to provide a small performance boost, so I've incorporated it
> into v27.
Won't Valgrind complain about this?
+pg_popcount_avx512(const char *buf, int bytes)
+ buf = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf);
+ val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
David
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
@ 2024-04-06 01:17 ` Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-06 01:17 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, Apr 06, 2024 at 12:08:14PM +1300, David Rowley wrote:
> Won't Valgrind complain about this?
>
> +pg_popcount_avx512(const char *buf, int bytes)
>
> + buf = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf);
>
> + val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
I haven't been able to generate any complaints, at least with some simple
tests. But I see your point. If this did cause such complaints, ISTM we'd
just want to add it to the suppression file. Otherwise, I think we'd have
to go back to the non-maskz approach (which I really wanted to avoid
because of the weird function overhead juggling) or find another way to do
a partial load into an __m512i.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-06 01:51 ` David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: David Rowley @ 2024-04-06 01:51 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, 6 Apr 2024 at 14:17, Nathan Bossart <[email protected]> wrote:
>
> On Sat, Apr 06, 2024 at 12:08:14PM +1300, David Rowley wrote:
> > Won't Valgrind complain about this?
> >
> > +pg_popcount_avx512(const char *buf, int bytes)
> >
> > + buf = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf);
> >
> > + val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
>
> I haven't been able to generate any complaints, at least with some simple
> tests. But I see your point. If this did cause such complaints, ISTM we'd
> just want to add it to the suppression file. Otherwise, I think we'd have
> to go back to the non-maskz approach (which I really wanted to avoid
> because of the weird function overhead juggling) or find another way to do
> a partial load into an __m512i.
[1] seems to think it's ok. If this is true then the following
shouldn't segfault:
The following seems to run without any issue and if I change the mask
to 1 it crashes, as you'd expect.
#include <immintrin.h>
#include <stdio.h>
int main(void)
{
__m512i val;
val = _mm512_maskz_loadu_epi8((__mmask64) 0, NULL);
printf("%llu\n", _mm512_reduce_add_epi64(val));
return 0;
}
gcc avx512.c -o avx512 -O0 -mavx512f -march=native
David
[1] https://stackoverflow.com/questions/54497141/when-using-a-mask-register-with-avx-512-load-and-stores...
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
@ 2024-04-06 19:41 ` Nathan Bossart <[email protected]>
2024-04-07 04:05 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
0 siblings, 2 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-06 19:41 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, Apr 06, 2024 at 02:51:39PM +1300, David Rowley wrote:
> On Sat, 6 Apr 2024 at 14:17, Nathan Bossart <[email protected]> wrote:
>> On Sat, Apr 06, 2024 at 12:08:14PM +1300, David Rowley wrote:
>> > Won't Valgrind complain about this?
>> >
>> > +pg_popcount_avx512(const char *buf, int bytes)
>> >
>> > + buf = (const char *) TYPEALIGN_DOWN(sizeof(__m512i), buf);
>> >
>> > + val = _mm512_maskz_loadu_epi8(mask, (const __m512i *) buf);
>>
>> I haven't been able to generate any complaints, at least with some simple
>> tests. But I see your point. If this did cause such complaints, ISTM we'd
>> just want to add it to the suppression file. Otherwise, I think we'd have
>> to go back to the non-maskz approach (which I really wanted to avoid
>> because of the weird function overhead juggling) or find another way to do
>> a partial load into an __m512i.
>
> [1] seems to think it's ok. If this is true then the following
> shouldn't segfault:
>
> The following seems to run without any issue and if I change the mask
> to 1 it crashes, as you'd expect.
Cool.
Here is what I have staged for commit, which I intend to do shortly. At
some point, I'd like to revisit converting TRY_POPCNT_FAST to a
configure-time check and maybe even moving the "fast" and "slow"
implementations to their own files, but since that's mostly for code
neatness and we are rapidly approaching the v17 deadline, I'm content to
leave that for v18.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-07 04:05 ` Nathan Bossart <[email protected]>
1 sibling, 0 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-07 04:05 UTC (permalink / raw)
To: David Rowley <[email protected]>; +Cc: Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sat, Apr 06, 2024 at 02:41:01PM -0500, Nathan Bossart wrote:
> Here is what I have staged for commit, which I intend to do shortly.
Committed.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-08 00:42 ` Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
1 sibling, 1 reply; 110+ messages in thread
From: Tom Lane @ 2024-04-08 00:42 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
Nathan Bossart <[email protected]> writes:
> Here is what I have staged for commit, which I intend to do shortly.
Today's Coverity run produced this warning, which seemingly was
triggered by one of these commits, but I can't make much sense
of it:
*** CID 1596255: Uninitialized variables (UNINIT)
/usr/lib/gcc/x86_64-linux-gnu/10/include/avxintrin.h: 1218 in _mm256_undefined_si256()
1214 extern __inline __m256i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
1215 _mm256_undefined_si256 (void)
1216 {
1217 __m256i __Y = __Y;
>>> CID 1596255: Uninitialized variables (UNINIT)
>>> Using uninitialized value "__Y".
1218 return __Y;
1219 }
I see the same code in my local copy of avxintrin.h,
and I quite agree that it looks like either an undefined
value or something that properly ought to be an error.
If we are calling this, why (and from where)?
Anyway, we can certainly just dismiss this warning if it
doesn't correspond to any real problem in our code.
But I thought I'd raise the question.
regards, tom lane
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
@ 2024-04-08 01:23 ` Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-08 01:23 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sun, Apr 07, 2024 at 08:42:12PM -0400, Tom Lane wrote:
> Today's Coverity run produced this warning, which seemingly was
> triggered by one of these commits, but I can't make much sense
> of it:
>
> *** CID 1596255: Uninitialized variables (UNINIT)
> /usr/lib/gcc/x86_64-linux-gnu/10/include/avxintrin.h: 1218 in _mm256_undefined_si256()
> 1214 extern __inline __m256i __attribute__((__gnu_inline__, __always_inline__, __artificial__))
> 1215 _mm256_undefined_si256 (void)
> 1216 {
> 1217 __m256i __Y = __Y;
>>>> CID 1596255: Uninitialized variables (UNINIT)
>>>> Using uninitialized value "__Y".
> 1218 return __Y;
> 1219 }
>
> I see the same code in my local copy of avxintrin.h,
> and I quite agree that it looks like either an undefined
> value or something that properly ought to be an error.
> If we are calling this, why (and from where)?
Nothing in these commits uses this, or even uses the 256-bit registers.
avxintrin.h is included by immintrin.h, which is probably why this is
showing up. I believe you're supposed to use immintrin.h for the
intrinsics used in these commits, so I don't immediately see a great way to
avoid this. The Intel documentation for _mm256_undefined_si256() [0]
indicates that it is intended to return "undefined elements," so it seems
like the use of an uninitialized variable might be intentional.
> Anyway, we can certainly just dismiss this warning if it
> doesn't correspond to any real problem in our code.
> But I thought I'd raise the question.
That's probably the right thing to do, unless there's some action we can
take to suppress this warning.
[0] https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_undefined_si256...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-08 01:30 ` Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-08 01:30 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Sun, Apr 07, 2024 at 08:23:32PM -0500, Nathan Bossart wrote:
> The Intel documentation for _mm256_undefined_si256() [0]
> indicates that it is intended to return "undefined elements," so it seems
> like the use of an uninitialized variable might be intentional.
See also https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git;h=72af61b122.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-08 01:35 ` Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Tom Lane @ 2024-04-08 01:35 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
Nathan Bossart <[email protected]> writes:
> On Sun, Apr 07, 2024 at 08:23:32PM -0500, Nathan Bossart wrote:
>> The Intel documentation for _mm256_undefined_si256() [0]
>> indicates that it is intended to return "undefined elements," so it seems
>> like the use of an uninitialized variable might be intentional.
> See also https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git;h=72af61b122.
Ah, interesting. That hasn't propagated to stable distros yet,
evidently (and even when it does, I wonder how soon Coverity
will understand it). Anyway, that does establish that it's
gcc's problem not ours. Thanks for digging!
regards, tom lane
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
@ 2024-04-18 02:44 ` Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-18 02:44 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Shankaran, Akash <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
It was brought to my attention [0] that we probably should be checking for
the OSXSAVE bit instead of the XSAVE bit when determining whether there's
support for the XGETBV instruction. IIUC that should indicate that both
the OS and the processor have XGETBV support (not just the processor).
I've attached a one-line patch to fix this.
[0] https://github.com/pgvector/pgvector/pull/519#issuecomment-2062804463
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-diff] osxsave.patch (485B, ../../20240418024459.GA3385227@nathanxps13/2-osxsave.patch)
download | inline diff:
diff --git a/src/port/pg_popcount_avx512_choose.c b/src/port/pg_popcount_avx512_choose.c
index ae3fa3d306..cc3e89e096 100644
--- a/src/port/pg_popcount_avx512_choose.c
+++ b/src/port/pg_popcount_avx512_choose.c
@@ -74,7 +74,7 @@ pg_popcount_avx512_available(void)
#else
#error cpuid instruction not available
#endif
- if ((exx[2] & (1 << 26)) == 0) /* xsave */
+ if ((exx[2] & (1 << 27)) == 0) /* osxsave */
return false;
/* Does XGETBV say the ZMM registers are enabled? */
^ permalink raw reply [nested|flat] 110+ messages in thread
* RE: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-18 18:12 ` Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Shankaran, Akash @ 2024-04-18 18:12 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>; Devulapalli, Raghuveer <[email protected]>
> It was brought to my attention [0] that we probably should be checking for the OSXSAVE bit instead of the XSAVE bit when determining whether there's support for the XGETBV instruction. IIUC that should indicate that both the OS and the processor have XGETBV support (not just the processor).
> I've attached a one-line patch to fix this.
> [0] https://github.com/pgvector/pgvector/pull/519#issuecomment-2062804463
Good find. I confirmed after speaking with an intel expert, and from the intel AVX-512 manual [0] section 14.3, which recommends to check bit27. From the manual:
"Prior to using Intel AVX, the application must identify that the operating system supports the XGETBV instruction,
the YMM register state, in addition to processor's support for YMM state management using XSAVE/XRSTOR and
AVX instructions. The following simplified sequence accomplishes both and is strongly recommended.
1) Detect CPUID.1:ECX.OSXSAVE[bit 27] = 1 (XGETBV enabled for application use1).
2) Issue XGETBV and verify that XCR0[2:1] = '11b' (XMM state and YMM state are enabled by OS).
3) detect CPUID.1:ECX.AVX[bit 28] = 1 (AVX instructions supported).
(Step 3 can be done in any order relative to 1 and 2.)"
It also seems that step 1 and step 2 need to be done prior to the CPUID OSXSAVE check in the popcount code.
[0]: https://cdrdv2.intel.com/v1/dl/getContent/671200
- Akash Shankaran
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
@ 2024-04-18 19:53 ` Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-18 19:53 UTC (permalink / raw)
To: Shankaran, Akash <[email protected]>; +Cc: Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>; Devulapalli, Raghuveer <[email protected]>
On Thu, Apr 18, 2024 at 06:12:22PM +0000, Shankaran, Akash wrote:
> Good find. I confirmed after speaking with an intel expert, and from the intel AVX-512 manual [0] section 14.3, which recommends to check bit27. From the manual:
>
> "Prior to using Intel AVX, the application must identify that the operating system supports the XGETBV instruction,
> the YMM register state, in addition to processor's support for YMM state management using XSAVE/XRSTOR and
> AVX instructions. The following simplified sequence accomplishes both and is strongly recommended.
> 1) Detect CPUID.1:ECX.OSXSAVE[bit 27] = 1 (XGETBV enabled for application use1).
> 2) Issue XGETBV and verify that XCR0[2:1] = '11b' (XMM state and YMM state are enabled by OS).
> 3) detect CPUID.1:ECX.AVX[bit 28] = 1 (AVX instructions supported).
> (Step 3 can be done in any order relative to 1 and 2.)"
Thanks for confirming. IIUC my patch should be sufficient, then.
> It also seems that step 1 and step 2 need to be done prior to the CPUID OSXSAVE check in the popcount code.
This seems to contradict the note about doing step 3 at any point, and
given step 1 is the OSXSAVE check, I'm not following what this means,
anyway.
I'm also wondering if we need to check that (_xgetbv(0) & 0xe6) == 0xe6
instead of just (_xgetbv(0) & 0xe0) != 0, as the status of the lower half
of some of the ZMM registers is stored in the SSE and AVX state [0]. I
don't know how likely it is that 0xe0 would succeed but 0xe6 wouldn't, but
we might as well make it correct.
[0] https://en.wikipedia.org/wiki/Control_register#cite_ref-23
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-18 21:01 ` Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-18 21:01 UTC (permalink / raw)
To: Devulapalli, Raghuveer <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 18, 2024 at 08:24:03PM +0000, Devulapalli, Raghuveer wrote:
>> This seems to contradict the note about doing step 3 at any point, and
>> given step 1 is the OSXSAVE check, I'm not following what this means,
>> anyway.
>
> It is recommended that you run the xgetbv code before you check for cpu
> features avx512-popcnt and avx512-bw. The way it is written now is the
> opposite order. I would also recommend splitting the cpuid feature check
> for avx512popcnt/avx512bw and xgetbv section into separate functions to
> make them modular. Something like:
>
> static inline
> int check_os_avx512_support(void)
> {
> // (1) run cpuid leaf 1 to check for xgetbv instruction support:
> unsigned int exx[4] = {0, 0, 0, 0};
> __get_cpuid(1, &exx[0], &exx[1], &exx[2], &exx[3]);
> if ((exx[2] & (1 << 27)) == 0) /* xsave */
> return false;
>
> /* Does XGETBV say the ZMM/YMM/XMM registers are enabled? */
> return (_xgetbv(0) & 0xe0) == 0xe0;
> }
>
>> I'm also wondering if we need to check that (_xgetbv(0) & 0xe6) == 0xe6
>> instead of just (_xgetbv(0) & 0xe0) != 0, as the status of the lower
>> half of some of the ZMM registers is stored in the SSE and AVX state
>> [0]. I don't know how likely it is that 0xe0 would succeed but 0xe6
>> wouldn't, but we might as well make it correct.
>
> This is correct. It needs to check all the 3 bits (XMM/YMM and ZMM). The
> way it is written is now is in-correct.
Thanks for the feedback. I've attached an updated patch.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* RE: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-18 21:29 ` Devulapalli, Raghuveer <[email protected]>
2024-04-18 21:59 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Devulapalli, Raghuveer @ 2024-04-18 21:29 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
> Thanks for the feedback. I've attached an updated patch.
(1) Shouldn't it be: return (_xgetbv(0) & 0xe6) == 0xe6; ? Otherwise zmm_regs_available() will return false.
(2) Nitpick: avx512_popcnt_available and avx512_bw_available() run the same cpuid leaf. You could combine them into one to avoid running cpuid twice. My apologies, I should have mentioned this before.
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
@ 2024-04-18 21:59 ` Nathan Bossart <[email protected]>
2024-04-18 22:11 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-18 21:59 UTC (permalink / raw)
To: Devulapalli, Raghuveer <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 18, 2024 at 09:29:55PM +0000, Devulapalli, Raghuveer wrote:
> (1) Shouldn't it be: return (_xgetbv(0) & 0xe6) == 0xe6; ? Otherwise
> zmm_regs_available() will return false..
Yes, that's a mistake. I fixed that in v3.
> (2) Nitpick: avx512_popcnt_available and avx512_bw_available() run the
> same cpuid leaf. You could combine them into one to avoid running cpuid
> twice. My apologies, I should have mentioned this before..
Good call. The byte-and-word instructions were a late addition to the
patch, so I missed this originally.
On that note, is it necessary to also check for avx512f? At the moment, we
are assuming that's supported if the other AVX-512 instructions are
available.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* RE: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 21:59 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-18 22:11 ` Devulapalli, Raghuveer <[email protected]>
2024-04-18 22:13 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Devulapalli, Raghuveer @ 2024-04-18 22:11 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
> On that note, is it necessary to also check for avx512f? At the moment, we are assuming that's supported if the other AVX-512 instructions are available.
No, it's not needed. There are no CPU's with avx512bw/avx512popcnt without avx512f. Unfortunately though, avx512popcnt does not mean avx512bw (I think the deprecated Xeon Phi processors falls in this category) which is why we need both.
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 21:59 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 22:11 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
@ 2024-04-18 22:13 ` Nathan Bossart <[email protected]>
2024-04-23 16:02 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
0 siblings, 1 reply; 110+ messages in thread
From: Nathan Bossart @ 2024-04-18 22:13 UTC (permalink / raw)
To: Devulapalli, Raghuveer <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 18, 2024 at 10:11:08PM +0000, Devulapalli, Raghuveer wrote:
>> On that note, is it necessary to also check for avx512f? At the moment,
>> we are assuming that's supported if the other AVX-512 instructions are
>> available.
>
> No, it's not needed. There are no CPU's with avx512bw/avx512popcnt
> without avx512f. Unfortunately though, avx512popcnt does not mean
> avx512bw (I think the deprecated Xeon Phi processors falls in this
> category) which is why we need both.
Makes sense, thanks. I'm planning to commit this fix sometime early next
week.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
* Re: Popcount optimization using AVX512
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 21:59 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 22:11 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 22:13 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
@ 2024-04-23 16:02 ` Nathan Bossart <[email protected]>
0 siblings, 0 replies; 110+ messages in thread
From: Nathan Bossart @ 2024-04-23 16:02 UTC (permalink / raw)
To: Devulapalli, Raghuveer <[email protected]>; +Cc: Shankaran, Akash <[email protected]>; Tom Lane <[email protected]>; David Rowley <[email protected]>; Ants Aasma <[email protected]>; Alvaro Herrera <[email protected]>; Amonson, Paul D <[email protected]>; Andres Freund <[email protected]>; Noah Misch <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] <[email protected]>
On Thu, Apr 18, 2024 at 05:13:58PM -0500, Nathan Bossart wrote:
> Makes sense, thanks. I'm planning to commit this fix sometime early next
> week.
Committed.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 110+ messages in thread
end of thread, other threads:[~2024-04-23 16:02 UTC | newest]
Thread overview: 110+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
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 v12 05/11] Add pg_ls_dir_metadata to list a dir with file metadata.. Justin Pryzby <[email protected]>
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 v9 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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]>
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]>
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 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 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 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 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 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 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 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 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 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 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]>
2024-04-01 01:17 Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 11:06 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-01 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 21:11 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 21:31 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:09 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-01 22:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-01 22:11 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 15:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 17:34 ` Re: Popcount optimization using AVX512 Alvaro Herrera <[email protected]>
2024-04-02 17:43 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-02 18:40 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 22:20 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 02:09 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 17:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-03 20:12 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-02 20:30 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-03 22:50 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 03:28 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-04 17:18 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-04 13:02 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-04 17:28 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 04:15 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 07:33 ` Re: Popcount optimization using AVX512 Ants Aasma <[email protected]>
2024-04-05 12:58 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 15:38 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-05 23:08 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 01:17 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-06 01:51 ` Re: Popcount optimization using AVX512 David Rowley <[email protected]>
2024-04-06 19:41 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-07 04:05 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 00:42 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-08 01:23 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:30 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-08 01:35 ` Re: Popcount optimization using AVX512 Tom Lane <[email protected]>
2024-04-18 02:44 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 18:12 ` RE: Popcount optimization using AVX512 Shankaran, Akash <[email protected]>
2024-04-18 19:53 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:01 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 21:29 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 21:59 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-18 22:11 ` RE: Popcount optimization using AVX512 Devulapalli, Raghuveer <[email protected]>
2024-04-18 22:13 ` Re: Popcount optimization using AVX512 Nathan Bossart <[email protected]>
2024-04-23 16:02 ` Re: Popcount optimization using AVX512 Nathan Bossart <[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