agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v24 09/11] pg_ls_*/pg_stat_file to show file *type*..
18+ messages / 5 participants
[nested] [flat]
* [PATCH v24 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e3d7df2dd7..55e9f5a8b2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25747,13 +25747,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25777,7 +25777,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -25800,13 +25800,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25828,14 +25828,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25858,7 +25858,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -25868,7 +25868,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25942,13 +25942,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9ff06f4158..6e8898029f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -753,5 +791,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fea405068..12a7391e73 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6202,16 +6202,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10951,41 +10951,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--mPTHnM80CEnHQ2WJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v24-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v30 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df2b345829..56189c26c3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25863,7 +25863,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -26872,13 +26872,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26900,13 +26900,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26928,14 +26928,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26958,7 +26958,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -26968,7 +26968,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27042,13 +27042,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 935a81287a..10bcb0b049 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -401,6 +402,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -420,7 +458,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -470,7 +508,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -539,10 +577,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -740,7 +778,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -755,5 +793,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 282656ef82..176501a87a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6303,16 +6303,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11523,41 +11523,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--ZwgA9U+XZDXt4+m+
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v30-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v26 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 017d761399..46e7ff089d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25814,7 +25814,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -26427,13 +26427,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26455,13 +26455,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26483,14 +26483,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26513,7 +26513,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -26523,7 +26523,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26597,13 +26597,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 0bf148435f..0855f5e88e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -753,5 +791,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 30c21a7c6d..5d80cfa6c1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11242,41 +11242,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--yKkOmjQZXRsvHRX8
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v26-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v22 10/10] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 16 +++---
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/bin/pg_rewind/libpq_fetch.c | 2 +-
src/include/catalog/pg_proc.dat | 38 ++++++-------
src/test/regress/expected/misc_functions.out | 38 ++++++-------
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 6 +-
7 files changed, 103 insertions(+), 63 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 809483feed..b8a0c2c3b1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25826,7 +25826,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25854,7 +25854,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25883,7 +25883,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25916,7 +25916,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25990,13 +25990,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 20b948b746..843d650ddd 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_stat_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -753,5 +791,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index a9535d3dd5..d05a9e962e 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -184,7 +184,7 @@ libpqProcessFileList(void)
* directory, they won't be copied correctly.
*/
sql =
- "SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
+ "SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, type='d' AS isdir,\n"
" pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
"FROM pg_ls_dir_recurse('.') files\n"
"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3f62aea9c9..e87b80ce47 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6193,16 +6193,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10933,13 +10933,13 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10949,13 +10949,13 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9147', descr => 'check pages of a relation',
proname => 'pg_relation_check_pages', procost => '10000', prorows => '20',
@@ -10967,21 +10967,21 @@
{ 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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
{ oid => '9981', descr => 'list all files in a directory recursively',
proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'text',
- proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
- prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+ proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+ prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
# 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 4be27fe21e..9bfc445d99 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
- path | filename | isdir
---------+----------------+-------
- pg_wal | archive_status | t
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
+ path | filename | type
+--------+----------------+------
+ pg_wal | archive_status | d
(1 row)
-- Check that expected columns are present
select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | access | modification | change | creation | isdir
-------+----------+------+--------+--------------+--------+----------+-------
+ path | filename | size | access | modification | change | creation | type
+------+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index fda95828d7..b87965a1bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
-- Check that expected columns are present
select * from pg_ls_dir_recurse('.') limit 0;
--
2.17.0
--d6Gm4EdcadzBjdND--
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v33 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 48 ++++++++--------
src/test/regress/expected/misc_functions.out | 58 ++++++++++----------
src/test/regress/expected/tablespace.out | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 121 insertions(+), 81 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e497f9a5d23..f4bfa239278 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25973,7 +25973,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -27358,13 +27358,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27386,13 +27386,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27487,14 +27487,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27517,7 +27517,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -27527,7 +27527,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27601,13 +27601,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index cc4fc4924ca..8401743efad 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -756,7 +794,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ee7793467d0..8f4c87c724d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11615,63 +11615,63 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalsnapdir' },
{ oid => '9859',
descr => 'list of files in the pg_logical/mappings directory',
proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalmapdir' },
{ oid => '9860',
descr => 'list of files in the pg_replslot/slot_name directory',
proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+ proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 0db07209cc9..6a6cec31a2e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -206,8 +206,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -270,67 +270,67 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_ls_archive_statusdir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_logdir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_logicalmapdir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_logicalsnapdir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_replslotdir('') limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_tmpdir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index b01ca30c7ab..68a58dd2f91 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -28,15 +28,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index bfa8e309e9a..54543c06efe 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -113,9 +113,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.1
--9CzcV6dAFIr7O1Ie
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v33-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v27 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a01971d5fe..0a7083efac 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25808,7 +25808,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -26858,13 +26858,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26886,13 +26886,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26914,14 +26914,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26944,7 +26944,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -26954,7 +26954,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27028,13 +27028,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 17e05b853e..af666d658b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -756,5 +794,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 491422c9f5..0a9d1e46bd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6302,16 +6302,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11493,41 +11493,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--19uQFt6ulqmgNgg1
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v27-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v32 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 48 ++++++++--------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/expected/tablespace.out | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 107 insertions(+), 67 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3650e3d71f2..5c517ed6347 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25960,7 +25960,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -27363,13 +27363,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27391,13 +27391,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27419,14 +27419,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27449,7 +27449,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -27459,7 +27459,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27533,13 +27533,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e907f399241..bef20178ea6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -756,7 +794,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8ad1827cbd..346c4c577c0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11599,63 +11599,63 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalsnapdir' },
{ oid => '9859',
descr => 'list of files in the pg_logical/mappings directory',
proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalmapdir' },
{ oid => '9860',
descr => 'list of files in the pg_replslot/slot_name directory',
proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+ proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index dc4470fe3ee..8b26b5811e4 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -263,32 +263,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index db77360abf6..67d3f5eed98 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -20,15 +20,15 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ca946d08bde..a5828414531 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -103,9 +103,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--Bne5rrxQd65beI7a
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v32-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v25 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f294730605..cd4375f75b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25740,13 +25740,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25770,7 +25770,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -25793,13 +25793,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25821,14 +25821,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25851,7 +25851,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -25861,7 +25861,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25935,13 +25935,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9ff06f4158..6e8898029f 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -753,5 +791,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3f43958423..414b087756 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6203,16 +6203,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10952,41 +10952,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index 1f4106cd9c..5b87ab4844 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -221,32 +221,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index 64d9d70874..0961cdc058 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -72,9 +72,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--SBikYMzjhZGK9d4p
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v25-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v28 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 36 ++++++------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 101 insertions(+), 61 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 402c10e30b..f81323771d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25836,7 +25836,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -26886,13 +26886,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26914,13 +26914,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26942,14 +26942,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -26972,7 +26972,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -26982,7 +26982,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27056,13 +27056,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 17e05b853e..af666d658b 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -756,5 +794,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 08eea899d3..2fc1793b5a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6319,16 +6319,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11510,41 +11510,41 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index ddd35b79f5..db3d64de3b 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -170,8 +170,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -234,32 +234,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 34f874d3f1..21f3e68392 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index c169e527d9..55bed7b9e0 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -81,9 +81,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--vk/v8fjDPiDepTtA
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v28-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v23 10/10] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 16 +++---
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/bin/pg_rewind/libpq_source.c | 2 +-
src/include/catalog/pg_proc.dat | 38 ++++++-------
src/test/regress/expected/misc_functions.out | 38 ++++++-------
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 6 +-
7 files changed, 103 insertions(+), 63 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1b6b6130c7..a9808cbac6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25817,7 +25817,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25845,7 +25845,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25874,7 +25874,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25907,7 +25907,7 @@ LATERAL pg_ls_dir_recurse(dir) AS a;
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -25981,13 +25981,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index ca2b577a8d..816710f4d5 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -36,11 +36,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void tuple_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -49,7 +50,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -399,6 +400,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_stat_dir_files()
@@ -418,7 +456,7 @@ tuple_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -468,7 +506,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -537,10 +575,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -738,7 +776,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -753,5 +791,5 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
diff --git a/src/bin/pg_rewind/libpq_source.c b/src/bin/pg_rewind/libpq_source.c
index 46c8b7fcd5..417be9b525 100644
--- a/src/bin/pg_rewind/libpq_source.c
+++ b/src/bin/pg_rewind/libpq_source.c
@@ -213,7 +213,7 @@ libpq_traverse_files(rewind_source *source, process_file_callback_t callback)
* directory, they won't be copied correctly.
*/
sql =
- "SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, isdir,\n"
+ "SELECT COALESCE(NULLIF(path,'.')||'/','')||filename, size, type='d' AS isdir,\n"
" pg_tablespace_location(pg_tablespace.oid) AS link_target\n"
"FROM pg_ls_dir_recurse('.') files\n"
"LEFT OUTER JOIN pg_tablespace ON files.path = 'pg_tblspc'\n"
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a995780dfc..90876e297b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6205,16 +6205,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -10947,13 +10947,13 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
@@ -10963,32 +10963,32 @@
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
{ oid => '9981', descr => 'list all files in a directory recursively',
proname => 'pg_ls_dir_recurse', prorows => '10000', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'text',
- proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargnames => '{dirname,path,filename,size,access,modification,change,creation,isdir}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
- prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.isdir from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.isdir) select * from ls" },
+ proallargtypes => '{text,text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargnames => '{dirname,path,filename,size,access,modification,change,creation,type}', proargmodes => '{i,o,o,o,o,o,o,o,o}',
+ prolang => 'sql', prosrc => "with recursive ls as (select dirname as path, * from pg_ls_dir_metadata(dirname, false, false) union all select coalesce(nullif(parent.path,'.')||'/','')||parent.filename, a.filename, a.size, a.access, a.modification, a.change, a.creation, a.type from ls as parent, lateral pg_ls_dir_metadata(parent.path||'/'||parent.filename, false, false) as a where parent.type='d') select * from ls" },
# 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 4be27fe21e..9bfc445d99 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -157,8 +157,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -222,38 +222,38 @@ 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
- path | filename | isdir
---------+----------------+-------
- pg_wal | archive_status | t
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
+ path | filename | type
+--------+----------------+------
+ pg_wal | archive_status | d
(1 row)
-- Check that expected columns are present
select * from pg_ls_dir_recurse('.') limit 0;
- path | filename | size | access | modification | change | creation | isdir
-------+----------+------+--------+--------------+--------+----------+-------
+ path | filename | size | access | modification | change | creation | type
+------+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1e1e02b589..025c9709a1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index fda95828d7..b87965a1bd 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -69,15 +69,15 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
-- Check that we at least succeed in recursing once, and that we don't show the leading dir prefix
-select path, filename, isdir from pg_ls_dir_recurse('.') where isdir and path='pg_wal';
+select path, filename, type from pg_ls_dir_recurse('.') where type='d' and path='pg_wal';
-- Check that expected columns are present
select * from pg_ls_dir_recurse('.') limit 0;
--
2.17.0
--mhjHhnbe5PrRcwjY--
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v31 09/11] pg_ls_*/pg_stat_file to show file *type*..
@ 2020-03-31 19:40 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Justin Pryzby @ 2020-03-31 19:40 UTC (permalink / raw)
..not just "isdir"
Also show special file types, now that their type is shown.
---
doc/src/sgml/func.sgml | 26 +++++----
src/backend/utils/adt/genfile.c | 58 ++++++++++++++++----
src/include/catalog/pg_proc.dat | 48 ++++++++--------
src/test/regress/expected/misc_functions.out | 30 +++++-----
src/test/regress/output/tablespace.source | 8 +--
src/test/regress/sql/misc_functions.sql | 4 +-
6 files changed, 107 insertions(+), 67 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04d2d2c830..ca329869cb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25875,7 +25875,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the specified directory, list the file and its
@@ -27278,13 +27278,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's log directory,
return the file's name, along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27306,13 +27306,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's write-ahead log (WAL) directory, list the
file's name along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special files types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27334,14 +27334,14 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the server's WAL archive status directory
(<filename>pg_wal/archive_status</filename>), list the file's name
along with the metadata columns returned by
<function>pg_stat_file</function>.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27364,7 +27364,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
For each file in the temporary directory within the given
@@ -27374,7 +27374,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
Directories are used for temporary files shared by parallel processes.
If <parameter>tablespace</parameter> is not provided, the
<literal>pg_default</literal> tablespace is examined.
- Filenames beginning with a dot and special file types are excluded.
+ Filenames beginning with a dot are excluded.
</para>
<para>
This function is restricted to superusers and members of
@@ -27448,13 +27448,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
<parameter>modification</parameter> <type>timestamp with time zone</type>,
<parameter>change</parameter> <type>timestamp with time zone</type>,
<parameter>creation</parameter> <type>timestamp with time zone</type>,
- <parameter>isdir</parameter> <type>boolean</type> )
+ <parameter>type</parameter> <type>char</type> )
</para>
<para>
Returns a record containing the file's size, last access time stamp,
last modification time stamp, last file status change time stamp (Unix
- platforms only), file creation time stamp (Windows only), and a flag
- indicating if it is a directory.
+ platforms only), file creation time stamp (Windows only), and a
+ character representing the file's type: regular (-), directory (d), link
+ or junction (l), character device (c), block device (b), fifo (p),
+ socket (s) or other/unknown (?).
</para>
<para>
This function is restricted to superusers by default, but other users
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e907f39924..bef20178ea 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -37,11 +37,12 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
+static char get_file_type(mode_t mode, const char *path);
static void values_from_stat(struct stat *fst, const char *path, Datum *values,
bool *nulls);
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_TYPE (1<<0) /* Show column: type */
#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 .. */
@@ -50,7 +51,7 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags
#define LS_DIR_SKIP_SPECIAL (1<<6) /* Do not show special file types */
/* Shortcut for common behavior */
-#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_SKIP_SPECIAL | LS_DIR_METADATA)
+#define LS_DIR_COMMON (LS_DIR_SKIP_HIDDEN | LS_DIR_METADATA)
/*
* Convert a "text" filename argument to C string, and check it's allowable.
@@ -402,6 +403,43 @@ pg_read_binary_file_all(PG_FUNCTION_ARGS)
return pg_read_binary_file(fcinfo);
}
+/* Return a character indicating the type of file, or '?' if unknown type */
+static char
+get_file_type(mode_t mode, const char *path)
+{
+ if (S_ISREG(mode))
+ return '-';
+
+ if (S_ISDIR(mode))
+ return 'd';
+#ifndef WIN32
+ if (S_ISLNK(mode))
+ return 'l';
+#else
+ if (pgwin32_is_junction(path))
+ return 'l';
+#endif
+
+#ifdef S_ISCHR
+ if (S_ISCHR(mode))
+ return 'c';
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode))
+ return 'b';
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode))
+ return 'p';
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode))
+ return 's';
+#endif
+
+ return '?';
+}
+
/*
* Populate values and nulls from fst and path.
* Used for pg_stat_file() and pg_ls_dir_files()
@@ -421,7 +459,7 @@ values_from_stat(struct stat *fst, const char *path, Datum *values, bool *nulls)
nulls[3] = true;
values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst->st_ctime));
#endif
- values[5] = BoolGetDatum(S_ISDIR(fst->st_mode));
+ values[5] = CharGetDatum(get_file_type(fst->st_mode, path));
}
/*
@@ -471,7 +509,7 @@ pg_stat_file(PG_FUNCTION_ARGS)
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
"creation", TIMESTAMPTZOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
- "isdir", BOOLOID, -1, 0);
+ "type", CHAROID, -1, 0);
BlessTupleDesc(tupdesc);
memset(nulls, false, sizeof(nulls));
@@ -540,10 +578,10 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
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));
+ /* type depends on metadata */
+ Assert(!(flags&LS_DIR_TYPE) || (flags&LS_DIR_METADATA));
+ /* Unreasonable to show type and skip dirs XXX */
+ Assert(!(flags&LS_DIR_TYPE) || !(flags&LS_DIR_SKIP_DIRS));
/* check the optional arguments */
if (PG_NARGS() == 3)
@@ -741,7 +779,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
@@ -756,7 +794,7 @@ 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);
+ LS_DIR_METADATA | LS_DIR_TYPE);
}
/*
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b1944b32fb..61b39094d0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6375,16 +6375,16 @@
{ oid => '2623', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o}',
- proargnames => '{filename,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file_1arg' },
{ oid => '3307', descr => 'get information about file',
proname => 'pg_stat_file', provolatile => 'v', prorettype => 'record',
proargtypes => 'text bool',
- proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proallargtypes => '{text,bool,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,i,o,o,o,o,o,o}',
- proargnames => '{filename,missing_ok,size,access,modification,change,creation,isdir}',
+ proargnames => '{filename,missing_ok,size,access,modification,change,creation,type}',
prosrc => 'pg_stat_file' },
{ oid => '2624', descr => 'read text from a file',
proname => 'pg_read_file', provolatile => 'v', prorettype => 'text',
@@ -11599,63 +11599,63 @@
{ oid => '3353', descr => 'list files in the log directory',
proname => 'pg_ls_logdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_logdir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_logdir' },
{ oid => '3354', descr => 'list of files in the WAL directory',
proname => 'pg_ls_waldir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_waldir' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_waldir' },
{ oid => '5031', descr => 'list of files in the archive_status directory',
proname => 'pg_ls_archive_statusdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_archive_statusdir' },
{ oid => '5029', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => '',
- proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{o,o,o,o,o,o,o}',
- proargnames => '{name,size,access,modification,change,creation,isdir}', prosrc => 'pg_ls_tmpdir_noargs' },
+ proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{o,o,o,o,o,o,o}',
+ proargnames => '{name,size,access,modification,change,creation,type}', prosrc => 'pg_ls_tmpdir_noargs' },
{ oid => '5030', descr => 'list files in the pgsql_tmp directory',
proname => 'pg_ls_tmpdir', procost => '10', prorows => '20', proretset => 't',
provolatile => 'v', prorettype => 'record', proargtypes => 'oid',
- proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{tablespace,name,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{oid,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{tablespace,name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_tmpdir_1arg' },
{ oid => '9858',
descr => 'list of files in the pg_logical/snapshots directory',
proname => 'pg_ls_logicalsnapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalsnapdir' },
{ oid => '9859',
descr => 'list of files in the pg_logical/mappings directory',
proname => 'pg_ls_logicalmapdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
- proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,isdir}',
+ proargtypes => '', proallargtypes => '{text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
+ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{name,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_logicalmapdir' },
{ oid => '9860',
descr => 'list of files in the pg_replslot/slot_name directory',
proname => 'pg_ls_replslotdir', procost => '10', prorows => '20',
proretset => 't', provolatile => 'v', prorettype => 'record',
- proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,bool}',
+ proargtypes => 'text', proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}',
proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{slot_name,name,size,access,modification,change,creation,isdir}',
+ proargnames => '{slot_name,name,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,bool,bool,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,i,i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,missing_ok,include_dot_dirs,filename,size,access,modification,change,creation,type}',
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,timestamptz,timestamptz,timestamptz,bool}', proargmodes => '{i,o,o,o,o,o,o,o}',
- proargnames => '{dirname,filename,size,access,modification,change,creation,isdir}',
+ proallargtypes => '{text,text,int8,timestamptz,timestamptz,timestamptz,timestamptz,char}', proargmodes => '{i,o,o,o,o,o,o,o}',
+ proargnames => '{dirname,filename,size,access,modification,change,creation,type}',
prosrc => 'pg_ls_dir_metadata_1arg' },
# hash partitioning constraint function
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index dc4470fe3e..8b26b5811e 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -199,8 +199,8 @@ select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
-- Test not-run-to-completion cases.
select * from pg_ls_waldir() limit 0;
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
@@ -263,32 +263,32 @@ select pg_ls_dir('does not exist'); -- fails with missingok=false
ERROR: could not open directory "does not exist": No such file or directory
-- Check that expected columns are present
select * from pg_stat_file('.') limit 0;
- size | access | modification | change | creation | isdir
-------+--------+--------------+--------+----------+-------
+ size | access | modification | change | creation | type
+------+--------+--------------+--------+----------+------
(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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-select filename, isdir from pg_ls_dir_metadata('.') where filename='.';
- filename | isdir
-----------+-------
- . | t
+select filename, type from pg_ls_dir_metadata('.') where filename='.';
+ filename | type
+----------+------
+ . | d
(1 row)
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
- filename | isdir
-----------+-------
+select filename, type from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+ filename | type
+----------+------
(0 rows)
-- Check that expected columns are present
select * from pg_ls_dir_metadata('.') limit 0;
- filename | size | access | modification | change | creation | isdir
-----------+------+--------+--------------+--------+----------+-------
+ filename | size | access | modification | change | creation | type
+----------+------+--------+--------------+--------+----------+------
(0 rows)
--
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1fc54528c7..5c97acd1e1 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -17,15 +17,15 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
-- 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 | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- This tests the missing_ok parameter. If that's not functioning, this would ERROR if the logdir doesn't exist yet.
-- The name='' condition is never true, so the function runs to completion but returns zero rows.
SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
- name | size | access | modification | change | creation | isdir
-------+------+--------+--------------+--------+----------+-------
+ name | size | access | modification | change | creation | type
+------+------+--------+--------------+--------+----------+------
(0 rows)
-- try setting and resetting some properties for the new tablespace
diff --git a/src/test/regress/sql/misc_functions.sql b/src/test/regress/sql/misc_functions.sql
index ca946d08bd..a582841453 100644
--- a/src/test/regress/sql/misc_functions.sql
+++ b/src/test/regress/sql/misc_functions.sql
@@ -103,9 +103,9 @@ select * from pg_stat_file('.') limit 0;
-- 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, type from pg_ls_dir_metadata('.') where filename='.';
-select filename, isdir from pg_ls_dir_metadata('.', false, false) where filename='.'; -- include_dot_dirs=false
+select filename, type 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;
--
2.17.0
--qZVVwWJgpX9Jzs7f
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v31-0010-Preserve-pg_stat_file-isdir.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
@ 2024-02-07 08:10 Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Peter Eisentraut @ 2024-02-07 08:10 UTC (permalink / raw)
To: pgsql-hackers; +Cc: vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
On 18.01.24 07:53, Peter Eisentraut wrote:
> On 17.01.24 13:15, vignesh C wrote:
>> One of the test has failed in CFBot at [1] with:
>> diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/domain.out
>> /tmp/cirrus-ci-build/src/test/regress/results/domain.out
>> --- /tmp/cirrus-ci-build/src/test/regress/expected/domain.out
>> 2024-01-14 15:40:01.793434601 +0000
>> +++ /tmp/cirrus-ci-build/src/test/regress/results/domain.out
>> 2024-01-14 15:42:23.013332625 +0000
>> @@ -1271,11 +1271,4 @@
>> FROM information_schema.domain_constraints
>> WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
>> ORDER BY constraint_name;
>> - constraint_catalog | constraint_schema | constraint_name |
>> check_clause
>> ---------------------+-------------------+------------------+-------------------
>> - regression | public | con_check | (VALUE > 0)
>> - regression | public | meow | (VALUE <
>> 11)
>> - regression | public | pos_int_check | (VALUE > 0)
>> - regression | public | pos_int_not_null | VALUE IS
>> NOT NULL
>> -(4 rows)
>> -
>> +ERROR: could not open relation with OID 36379
>>
>> [1] - https://cirrus-ci.com/task/4536440638406656
>> [2] -
>> https://api.cirrus-ci.com/v1/artifact/task/4536440638406656/log/src/test/regress/regression.diffs
>
> Interesting. I couldn't reproduce this locally, even across different
> operating systems. The cfbot failures appear to be sporadic, but also
> happening across multiple systems, so it's clearly not just a local
> environment failure. Can anyone else perhaps reproduce this locally?
This patch set needed a rebase, so here it is.
About the sporadic test failure above, I think that is an existing issue
that is just now exposed through some test timing changes. The
pg_get_expr() function used in information_schema.check_constraints has
no locking against concurrent drops of tables. I think in this
particular case, the tests "domain" and "alter_table" are prone to this
conflict. If I move "domain" to a separate test group, the issue goes
away. I'll start a separate discussion about this issue.
From cd48c8eb8aa5958020e899b8406cbd7866805db5 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 23 Nov 2023 07:35:32 +0100
Subject: [PATCH v3 1/2] Add tests for domain-related information schema views
Discussion: https://www.postgresql.org/message-id/flat/9ec24d7b-633d-463a-84c6-7acff769c9e8%40eisentraut.org
---
src/test/regress/expected/domain.out | 47 ++++++++++++++++++++++++++++
src/test/regress/sql/domain.sql | 24 ++++++++++++++
2 files changed, 71 insertions(+)
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 6d94e84414a..e70aebd70c0 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -1207,3 +1207,50 @@ create domain testdomain1 as int constraint unsigned check (value > 0);
alter domain testdomain1 rename constraint unsigned to unsigned_foo;
alter domain testdomain1 drop constraint unsigned_foo;
drop domain testdomain1;
+--
+-- Information schema
+--
+SELECT * FROM information_schema.column_domain_usage
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+ domain_catalog | domain_schema | domain_name | table_catalog | table_schema | table_name | column_name
+----------------+---------------+-------------+---------------+--------------+------------+-------------
+ regression | public | con | regression | public | domcontest | col1
+ regression | public | dom | regression | public | domview | col1
+ regression | public | things | regression | public | thethings | stuff
+(3 rows)
+
+SELECT * FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY constraint_name;
+ constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
+--------------------+-------------------+-----------------+----------------+---------------+-------------+---------------+--------------------
+ regression | public | con_check | regression | public | con | NO | NO
+ regression | public | meow | regression | public | things | NO | NO
+ regression | public | pos_int_check | regression | public | pos_int | NO | NO
+(3 rows)
+
+SELECT * FROM information_schema.domains
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+ domain_catalog | domain_schema | domain_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | domain_default | udt_catalog | udt_schema | udt_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier
+----------------+---------------+-------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+----------------+-------------+------------+----------+---------------+--------------+------------+---------------------+----------------
+ regression | public | con | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | dom | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | pos_int | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | things | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+(4 rows)
+
+SELECT * FROM information_schema.check_constraints
+ WHERE (constraint_schema, constraint_name)
+ IN (SELECT constraint_schema, constraint_name
+ FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
+ ORDER BY constraint_name;
+ constraint_catalog | constraint_schema | constraint_name | check_clause
+--------------------+-------------------+-----------------+--------------
+ regression | public | con_check | (VALUE > 0)
+ regression | public | meow | (VALUE < 11)
+ regression | public | pos_int_check | (VALUE > 0)
+(3 rows)
+
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 745f5d5fd2b..813048c19f5 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -809,3 +809,27 @@ CREATE TABLE thethings (stuff things);
alter domain testdomain1 rename constraint unsigned to unsigned_foo;
alter domain testdomain1 drop constraint unsigned_foo;
drop domain testdomain1;
+
+
+--
+-- Information schema
+--
+
+SELECT * FROM information_schema.column_domain_usage
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+
+SELECT * FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY constraint_name;
+
+SELECT * FROM information_schema.domains
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+
+SELECT * FROM information_schema.check_constraints
+ WHERE (constraint_schema, constraint_name)
+ IN (SELECT constraint_schema, constraint_name
+ FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
+ ORDER BY constraint_name;
base-commit: b83033c3cff556d520281aaec399e47b4f11edbe
--
2.43.0
From d34881a605d43241686816aa00603aa1b4a55179 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 6 Feb 2024 14:55:54 +0100
Subject: [PATCH v3 2/2] Catalog domain not-null constraints
This applies the explicit catalog representation of not-null
constraints introduced by b0e96f3119 for table constraints also to
domain not-null constraints.
TODO: catversion
Discussion: https://www.postgresql.org/message-id/flat/9ec24d7b-633d-463a-84c6-7acff769c9e8%40eisentraut.org
---
src/backend/catalog/information_schema.sql | 2 +-
src/backend/catalog/pg_constraint.c | 40 +++
src/backend/commands/typecmds.c | 320 +++++++++++++++------
src/backend/utils/adt/ruleutils.c | 22 +-
src/backend/utils/cache/typcache.c | 2 +-
src/bin/pg_dump/pg_dump.c | 2 +-
src/include/catalog/pg_constraint.h | 1 +
src/test/regress/expected/domain.out | 49 +++-
src/test/regress/sql/domain.sql | 26 ++
9 files changed, 351 insertions(+), 113 deletions(-)
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c402ae72741..76c78c0d184 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -445,7 +445,7 @@ CREATE VIEW check_constraints AS
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
rs.nspname::information_schema.sql_identifier AS constraint_schema,
con.conname::information_schema.sql_identifier AS constraint_name,
- pg_catalog.format('%s IS NOT NULL', at.attname)::information_schema.character_data AS check_clause
+ pg_catalog.format('%s IS NOT NULL', coalesce(at.attname, 'VALUE'))::information_schema.character_data AS check_clause
FROM pg_constraint con
LEFT JOIN pg_namespace rs ON rs.oid = con.connamespace
LEFT JOIN pg_class c ON c.oid = con.conrelid
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0a95608179d..59ca40b7c23 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -631,6 +631,46 @@ findNotNullConstraint(Oid relid, const char *colname)
return findNotNullConstraintAttnum(relid, attnum);
}
+HeapTuple
+findDomainNotNullConstraint(Oid typid)
+{
+ Relation pg_constraint;
+ HeapTuple conTup,
+ retval = NULL;
+ SysScanDesc scan;
+ ScanKeyData key;
+
+ pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(typid));
+ scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId,
+ true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(conTup = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(conTup);
+
+ /*
+ * We're looking for a NOTNULL constraint that's marked validated.
+ */
+ if (con->contype != CONSTRAINT_NOTNULL)
+ continue;
+ if (!con->convalidated)
+ continue;
+
+ /* Found it */
+ retval = heap_copytuple(conTup);
+ break;
+ }
+
+ systable_endscan(scan);
+ table_close(pg_constraint, AccessShareLock);
+
+ return retval;
+}
+
/*
* Given a pg_constraint tuple for a not-null constraint, return the column
* number it is for.
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a400fb39f67..42ee58aab70 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -126,15 +126,19 @@ static Oid findTypeSubscriptingFunction(List *procname, Oid typeOid);
static Oid findRangeSubOpclass(List *opcname, Oid subtype);
static Oid findRangeCanonicalFunction(List *procname, Oid typeOid);
static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype);
-static void validateDomainConstraint(Oid domainoid, char *ccbin);
+static void validateDomainCheckConstraint(Oid domainoid, char *ccbin);
+static void validateDomainNotNullConstraint(Oid domainoid);
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
static void checkEnumOwner(HeapTuple tup);
-static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
- Oid baseTypeOid,
- int typMod, Constraint *constr,
- const char *domainName, ObjectAddress *constrAddr);
+static char *domainAddCheckConstraint(Oid domainOid, Oid domainNamespace,
+ Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr);
static Node *replace_domain_constraint_value(ParseState *pstate,
ColumnRef *cref);
+static void domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr);
static void AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
HeapTuple tup, Relation catalog,
AlterTypeRecurseParams *atparams);
@@ -1105,9 +1109,15 @@ DefineDomain(CreateDomainStmt *stmt)
switch (constr->contype)
{
case CONSTR_CHECK:
- domainAddConstraint(address.objectId, domainNamespace,
- basetypeoid, basetypeMod,
- constr, domainName, NULL);
+ domainAddCheckConstraint(address.objectId, domainNamespace,
+ basetypeoid, basetypeMod,
+ constr, domainName, NULL);
+ break;
+
+ case CONSTR_NOTNULL:
+ domainAddNotNullConstraint(address.objectId, domainNamespace,
+ basetypeoid, basetypeMod,
+ constr, domainName, NULL);
break;
/* Other constraint types were fully processed above */
@@ -2723,66 +2733,32 @@ AlterDomainNotNull(List *names, bool notNull)
return address;
}
- /* Adding a NOT NULL constraint requires checking existing columns */
if (notNull)
{
- List *rels;
- ListCell *rt;
+ Constraint *constr;
- /* Fetch relation list with attributes based on this domain */
- /* ShareLock is sufficient to prevent concurrent data changes */
+ constr = makeNode(Constraint);
+ constr->contype = CONSTR_NOTNULL;
+ constr->initially_valid = true;
+ constr->location = -1;
- rels = get_rels_with_domain(domainoid, ShareLock);
+ domainAddNotNullConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), NULL);
- foreach(rt, rels)
- {
- RelToCheck *rtc = (RelToCheck *) lfirst(rt);
- Relation testrel = rtc->rel;
- TupleDesc tupdesc = RelationGetDescr(testrel);
- TupleTableSlot *slot;
- TableScanDesc scan;
- Snapshot snapshot;
-
- /* Scan all tuples in this relation */
- snapshot = RegisterSnapshot(GetLatestSnapshot());
- scan = table_beginscan(testrel, snapshot, 0, NULL);
- slot = table_slot_create(testrel, NULL);
- while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
- {
- int i;
+ validateDomainNotNullConstraint(domainoid);
+ }
+ else
+ {
+ HeapTuple conTup;
+ ObjectAddress conobj;
- /* Test attributes that are of the domain */
- for (i = 0; i < rtc->natts; i++)
- {
- int attnum = rtc->atts[i];
- Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+ conTup = findDomainNotNullConstraint(domainoid);
+ if (conTup == NULL)
+ elog(ERROR, "could not find not-null constraint on domain \"%s\"", NameStr(typTup->typname));
- if (slot_attisnull(slot, attnum))
- {
- /*
- * In principle the auxiliary information for this
- * error should be errdatatype(), but errtablecol()
- * seems considerably more useful in practice. Since
- * this code only executes in an ALTER DOMAIN command,
- * the client should already know which domain is in
- * question.
- */
- ereport(ERROR,
- (errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("column \"%s\" of table \"%s\" contains null values",
- NameStr(attr->attname),
- RelationGetRelationName(testrel)),
- errtablecol(testrel, attnum)));
- }
- }
- }
- ExecDropSingleTupleTableSlot(slot);
- table_endscan(scan);
- UnregisterSnapshot(snapshot);
-
- /* Close each rel after processing, but keep lock */
- table_close(testrel, NoLock);
- }
+ ObjectAddressSet(conobj, ConstraintRelationId, ((Form_pg_constraint) GETSTRUCT(conTup))->oid);
+ performDeletion(&conobj, DROP_RESTRICT, 0);
}
/*
@@ -2863,10 +2839,17 @@ AlterDomainDropConstraint(List *names, const char *constrName,
/* There can be at most one matching row */
if ((contup = systable_getnext(conscan)) != NULL)
{
+ Form_pg_constraint construct = (Form_pg_constraint) GETSTRUCT(contup);
ObjectAddress conobj;
+ if (construct->contype == CONSTRAINT_NOTNULL)
+ {
+ ((Form_pg_type) GETSTRUCT(tup))->typnotnull = false;
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+ }
+
conobj.classId = ConstraintRelationId;
- conobj.objectId = ((Form_pg_constraint) GETSTRUCT(contup))->oid;
+ conobj.objectId = construct->oid;
conobj.objectSubId = 0;
performDeletion(&conobj, behavior, 0);
@@ -2947,6 +2930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
switch (constr->contype)
{
case CONSTR_CHECK:
+ case CONSTR_NOTNULL:
/* processed below */
break;
@@ -2989,29 +2973,46 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
break;
}
- /*
- * Since all other constraint types throw errors, this must be a check
- * constraint. First, process the constraint expression and add an entry
- * to pg_constraint.
- */
+ if (constr->contype == CONSTR_CHECK)
+ {
+ /*
+ * First, process the constraint expression and add an entry to
+ * pg_constraint.
+ */
- ccbin = domainAddConstraint(domainoid, typTup->typnamespace,
- typTup->typbasetype, typTup->typtypmod,
- constr, NameStr(typTup->typname), constrAddr);
+ ccbin = domainAddCheckConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), constrAddr);
- /*
- * If requested to validate the constraint, test all values stored in the
- * attributes based on the domain the constraint is being added to.
- */
- if (!constr->skip_validation)
- validateDomainConstraint(domainoid, ccbin);
- /*
- * We must send out an sinval message for the domain, to ensure that any
- * dependent plans get rebuilt. Since this command doesn't change the
- * domain's pg_type row, that won't happen automatically; do it manually.
- */
- CacheInvalidateHeapTuple(typrel, tup, NULL);
+ /*
+ * If requested to validate the constraint, test all values stored in
+ * the attributes based on the domain the constraint is being added
+ * to.
+ */
+ if (!constr->skip_validation)
+ validateDomainCheckConstraint(domainoid, ccbin);
+
+ /*
+ * We must send out an sinval message for the domain, to ensure that
+ * any dependent plans get rebuilt. Since this command doesn't change
+ * the domain's pg_type row, that won't happen automatically; do it
+ * manually.
+ */
+ CacheInvalidateHeapTuple(typrel, tup, NULL);
+ }
+ else if (constr->contype == CONSTR_NOTNULL)
+ {
+ domainAddNotNullConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), constrAddr);
+
+ if (!constr->skip_validation)
+ validateDomainNotNullConstraint(domainoid);
+
+ typTup->typnotnull = true;
+ CatalogTupleUpdate(typrel, &tup->t_self, tup);
+ }
ObjectAddressSet(address, TypeRelationId, domainoid);
@@ -3096,7 +3097,7 @@ AlterDomainValidateConstraint(List *names, const char *constrName)
val = SysCacheGetAttrNotNull(CONSTROID, tuple, Anum_pg_constraint_conbin);
conbin = TextDatumGetCString(val);
- validateDomainConstraint(domainoid, conbin);
+ validateDomainCheckConstraint(domainoid, conbin);
/*
* Now update the catalog, while we have the door open.
@@ -3123,7 +3124,68 @@ AlterDomainValidateConstraint(List *names, const char *constrName)
}
static void
-validateDomainConstraint(Oid domainoid, char *ccbin)
+validateDomainNotNullConstraint(Oid domainoid)
+{
+ List *rels;
+ ListCell *rt;
+
+ /* Fetch relation list with attributes based on this domain */
+ /* ShareLock is sufficient to prevent concurrent data changes */
+
+ rels = get_rels_with_domain(domainoid, ShareLock);
+
+ foreach(rt, rels)
+ {
+ RelToCheck *rtc = (RelToCheck *) lfirst(rt);
+ Relation testrel = rtc->rel;
+ TupleDesc tupdesc = RelationGetDescr(testrel);
+ TupleTableSlot *slot;
+ TableScanDesc scan;
+ Snapshot snapshot;
+
+ /* Scan all tuples in this relation */
+ snapshot = RegisterSnapshot(GetLatestSnapshot());
+ scan = table_beginscan(testrel, snapshot, 0, NULL);
+ slot = table_slot_create(testrel, NULL);
+ while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
+ {
+ int i;
+
+ /* Test attributes that are of the domain */
+ for (i = 0; i < rtc->natts; i++)
+ {
+ int attnum = rtc->atts[i];
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (slot_attisnull(slot, attnum))
+ {
+ /*
+ * In principle the auxiliary information for this error
+ * should be errdatatype(), but errtablecol() seems
+ * considerably more useful in practice. Since this code
+ * only executes in an ALTER DOMAIN command, the client
+ * should already know which domain is in question.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("column \"%s\" of table \"%s\" contains null values",
+ NameStr(attr->attname),
+ RelationGetRelationName(testrel)),
+ errtablecol(testrel, attnum)));
+ }
+ }
+ }
+ ExecDropSingleTupleTableSlot(slot);
+ table_endscan(scan);
+ UnregisterSnapshot(snapshot);
+
+ /* Close each rel after processing, but keep lock */
+ table_close(testrel, NoLock);
+ }
+}
+
+static void
+validateDomainCheckConstraint(Oid domainoid, char *ccbin)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
@@ -3429,12 +3491,12 @@ checkDomainOwner(HeapTuple tup)
}
/*
- * domainAddConstraint - code shared between CREATE and ALTER DOMAIN
+ * domainAddCheckConstraint - code shared between CREATE and ALTER DOMAIN
*/
static char *
-domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
- int typMod, Constraint *constr,
- const char *domainName, ObjectAddress *constrAddr)
+domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr)
{
Node *expr;
char *ccbin;
@@ -3442,6 +3504,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CoerceToDomainValue *domVal;
Oid ccoid;
+ Assert(constr->contype == CONSTR_CHECK);
+
/*
* Assign or validate constraint name
*/
@@ -3562,9 +3626,10 @@ replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
{
/*
* Check for a reference to "value", and if that's what it is, replace
- * with a CoerceToDomainValue as prepared for us by domainAddConstraint.
- * (We handle VALUE as a name, not a keyword, to avoid breaking a lot of
- * applications that have used VALUE as a column name in the past.)
+ * with a CoerceToDomainValue as prepared for us by
+ * domainAddCheckConstraint. (We handle VALUE as a name, not a keyword, to
+ * avoid breaking a lot of applications that have used VALUE as a column
+ * name in the past.)
*/
if (list_length(cref->fields) == 1)
{
@@ -3584,6 +3649,79 @@ replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
return NULL;
}
+/*
+ * domainAddNotNullConstraint - code shared between CREATE and ALTER DOMAIN
+ */
+static void
+domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr)
+{
+ Oid ccoid;
+
+ Assert(constr->contype == CONSTR_NOTNULL);
+
+ /*
+ * Assign or validate constraint name
+ */
+ if (constr->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
+ domainOid,
+ constr->conname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for domain \"%s\" already exists",
+ constr->conname, domainName)));
+ }
+ else
+ constr->conname = ChooseConstraintName(domainName,
+ NULL,
+ "not_null",
+ domainNamespace,
+ NIL);
+
+ /*
+ * Store the constraint in pg_constraint
+ */
+ ccoid =
+ CreateConstraintEntry(constr->conname, /* Constraint Name */
+ domainNamespace, /* namespace */
+ CONSTRAINT_NOTNULL, /* Constraint Type */
+ false, /* Is Deferrable */
+ false, /* Is Deferred */
+ !constr->skip_validation, /* Is Validated */
+ InvalidOid, /* no parent constraint */
+ InvalidOid, /* not a relation constraint */
+ NULL,
+ 0,
+ 0,
+ domainOid, /* domain constraint */
+ InvalidOid, /* no associated index */
+ InvalidOid, /* Foreign key fields */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ NULL,
+ 0,
+ ' ',
+ NULL, /* not an exclusion constraint */
+ NULL,
+ NULL,
+ true, /* is local */
+ 0, /* inhcount */
+ false, /* connoinherit */
+ false, /* conwithoutoverlaps */
+ false); /* is_internal */
+
+ if (constrAddr)
+ ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
+}
+
/*
* Execute ALTER TYPE RENAME
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b625f471a84..e57bbcdbc05 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2494,15 +2494,23 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
}
case CONSTRAINT_NOTNULL:
{
- AttrNumber attnum;
+ if (conForm->conrelid)
+ {
+ AttrNumber attnum;
- attnum = extractNotNullColumn(tup);
+ attnum = extractNotNullColumn(tup);
- appendStringInfo(&buf, "NOT NULL %s",
- quote_identifier(get_attname(conForm->conrelid,
- attnum, false)));
- if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
- appendStringInfoString(&buf, " NO INHERIT");
+ appendStringInfo(&buf, "NOT NULL %s",
+ quote_identifier(get_attname(conForm->conrelid,
+ attnum, false)));
+ if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
+ appendStringInfoString(&buf, " NO INHERIT");
+ }
+ else if (conForm->contypid)
+ {
+ /* conkey is null for domain not-null constraints */
+ appendStringInfoString(&buf, "NOT NULL VALUE");
+ }
break;
}
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 84fc83cb11f..2d689242b65 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -1069,7 +1069,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
Expr *check_expr;
DomainConstraintState *r;
- /* Ignore non-CHECK constraints (presently, shouldn't be any) */
+ /* Ignore non-CHECK constraints */
if (c->contype != CONSTRAINT_CHECK)
continue;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 348748bae53..6261d3e0fb7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7831,7 +7831,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
"pg_catalog.pg_get_constraintdef(oid) AS consrc, "
"convalidated "
"FROM pg_catalog.pg_constraint "
- "WHERE contypid = $1 "
+ "WHERE contypid = $1 AND contype = 'c' "
"ORDER BY conname");
ExecuteSqlStatement(fout, query->data);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 01e6bc21cd1..4e738b50b2e 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -257,6 +257,7 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
extern HeapTuple findNotNullConstraintAttnum(Oid relid, AttrNumber attnum);
extern HeapTuple findNotNullConstraint(Oid relid, const char *colname);
+extern HeapTuple findDomainNotNullConstraint(Oid typid);
extern AttrNumber extractNotNullColumn(HeapTuple constrTup);
extern bool AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
bool is_no_inherit);
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index e70aebd70c0..799b268ac76 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -798,6 +798,29 @@ alter domain con drop constraint nonexistent;
ERROR: constraint "nonexistent" of domain "con" does not exist
alter domain con drop constraint if exists nonexistent;
NOTICE: constraint "nonexistent" of domain "con" does not exist, skipping
+-- not-null constraints
+create domain connotnull integer;
+create table domconnotnulltest
+( col1 connotnull
+, col2 connotnull
+);
+insert into domconnotnulltest default values;
+alter domain connotnull add not null value; -- fails
+ERROR: column "col1" of table "domconnotnulltest" contains null values
+update domconnotnulltest set col1 = 5;
+alter domain connotnull add not null value; -- fails
+ERROR: column "col2" of table "domconnotnulltest" contains null values
+update domconnotnulltest set col2 = 6;
+alter domain connotnull add constraint constr1 not null value;
+update domconnotnulltest set col1 = null; -- fails
+ERROR: domain connotnull does not allow null values
+alter domain connotnull drop constraint constr1;
+update domconnotnulltest set col1 = null;
+drop domain connotnull cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to column col2 of table domconnotnulltest
+drop cascades to column col1 of table domconnotnulltest
+drop table domconnotnulltest;
-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
create domain things AS INT;
CREATE TABLE thethings (stuff things);
@@ -1223,12 +1246,13 @@ SELECT * FROM information_schema.column_domain_usage
SELECT * FROM information_schema.domain_constraints
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
ORDER BY constraint_name;
- constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
---------------------+-------------------+-----------------+----------------+---------------+-------------+---------------+--------------------
- regression | public | con_check | regression | public | con | NO | NO
- regression | public | meow | regression | public | things | NO | NO
- regression | public | pos_int_check | regression | public | pos_int | NO | NO
-(3 rows)
+ constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
+--------------------+-------------------+------------------+----------------+---------------+-------------+---------------+--------------------
+ regression | public | con_check | regression | public | con | NO | NO
+ regression | public | meow | regression | public | things | NO | NO
+ regression | public | pos_int_check | regression | public | pos_int | NO | NO
+ regression | public | pos_int_not_null | regression | public | pos_int | NO | NO
+(4 rows)
SELECT * FROM information_schema.domains
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
@@ -1247,10 +1271,11 @@ SELECT * FROM information_schema.check_constraints
FROM information_schema.domain_constraints
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
ORDER BY constraint_name;
- constraint_catalog | constraint_schema | constraint_name | check_clause
---------------------+-------------------+-----------------+--------------
- regression | public | con_check | (VALUE > 0)
- regression | public | meow | (VALUE < 11)
- regression | public | pos_int_check | (VALUE > 0)
-(3 rows)
+ constraint_catalog | constraint_schema | constraint_name | check_clause
+--------------------+-------------------+------------------+-------------------
+ regression | public | con_check | (VALUE > 0)
+ regression | public | meow | (VALUE < 11)
+ regression | public | pos_int_check | (VALUE > 0)
+ regression | public | pos_int_not_null | VALUE IS NOT NULL
+(4 rows)
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 813048c19f5..0e71f040040 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -469,6 +469,32 @@
alter domain con drop constraint nonexistent;
alter domain con drop constraint if exists nonexistent;
+-- not-null constraints
+create domain connotnull integer;
+create table domconnotnulltest
+( col1 connotnull
+, col2 connotnull
+);
+
+insert into domconnotnulltest default values;
+alter domain connotnull add not null value; -- fails
+
+update domconnotnulltest set col1 = 5;
+alter domain connotnull add not null value; -- fails
+
+update domconnotnulltest set col2 = 6;
+
+alter domain connotnull add constraint constr1 not null value;
+
+update domconnotnulltest set col1 = null; -- fails
+
+alter domain connotnull drop constraint constr1;
+
+update domconnotnulltest set col1 = null;
+
+drop domain connotnull cascade;
+drop table domconnotnulltest;
+
-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
create domain things AS INT;
CREATE TABLE thethings (stuff things);
--
2.43.0
Attachments:
[text/plain] v3-0001-Add-tests-for-domain-related-information-schema-v.patch (7.3K, ../../[email protected]/2-v3-0001-Add-tests-for-domain-related-information-schema-v.patch)
download | inline diff:
From cd48c8eb8aa5958020e899b8406cbd7866805db5 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Thu, 23 Nov 2023 07:35:32 +0100
Subject: [PATCH v3 1/2] Add tests for domain-related information schema views
Discussion: https://www.postgresql.org/message-id/flat/9ec24d7b-633d-463a-84c6-7acff769c9e8%40eisentraut.org
---
src/test/regress/expected/domain.out | 47 ++++++++++++++++++++++++++++
src/test/regress/sql/domain.sql | 24 ++++++++++++++
2 files changed, 71 insertions(+)
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 6d94e84414a..e70aebd70c0 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -1207,3 +1207,50 @@ create domain testdomain1 as int constraint unsigned check (value > 0);
alter domain testdomain1 rename constraint unsigned to unsigned_foo;
alter domain testdomain1 drop constraint unsigned_foo;
drop domain testdomain1;
+--
+-- Information schema
+--
+SELECT * FROM information_schema.column_domain_usage
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+ domain_catalog | domain_schema | domain_name | table_catalog | table_schema | table_name | column_name
+----------------+---------------+-------------+---------------+--------------+------------+-------------
+ regression | public | con | regression | public | domcontest | col1
+ regression | public | dom | regression | public | domview | col1
+ regression | public | things | regression | public | thethings | stuff
+(3 rows)
+
+SELECT * FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY constraint_name;
+ constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
+--------------------+-------------------+-----------------+----------------+---------------+-------------+---------------+--------------------
+ regression | public | con_check | regression | public | con | NO | NO
+ regression | public | meow | regression | public | things | NO | NO
+ regression | public | pos_int_check | regression | public | pos_int | NO | NO
+(3 rows)
+
+SELECT * FROM information_schema.domains
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+ domain_catalog | domain_schema | domain_name | data_type | character_maximum_length | character_octet_length | character_set_catalog | character_set_schema | character_set_name | collation_catalog | collation_schema | collation_name | numeric_precision | numeric_precision_radix | numeric_scale | datetime_precision | interval_type | interval_precision | domain_default | udt_catalog | udt_schema | udt_name | scope_catalog | scope_schema | scope_name | maximum_cardinality | dtd_identifier
+----------------+---------------+-------------+-----------+--------------------------+------------------------+-----------------------+----------------------+--------------------+-------------------+------------------+----------------+-------------------+-------------------------+---------------+--------------------+---------------+--------------------+----------------+-------------+------------+----------+---------------+--------------+------------+---------------------+----------------
+ regression | public | con | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | dom | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | pos_int | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+ regression | public | things | integer | | | | | | | | | 32 | 2 | 0 | | | | | regression | pg_catalog | int4 | | | | | 1
+(4 rows)
+
+SELECT * FROM information_schema.check_constraints
+ WHERE (constraint_schema, constraint_name)
+ IN (SELECT constraint_schema, constraint_name
+ FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
+ ORDER BY constraint_name;
+ constraint_catalog | constraint_schema | constraint_name | check_clause
+--------------------+-------------------+-----------------+--------------
+ regression | public | con_check | (VALUE > 0)
+ regression | public | meow | (VALUE < 11)
+ regression | public | pos_int_check | (VALUE > 0)
+(3 rows)
+
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 745f5d5fd2b..813048c19f5 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -809,3 +809,27 @@ CREATE TABLE thethings (stuff things);
alter domain testdomain1 rename constraint unsigned to unsigned_foo;
alter domain testdomain1 drop constraint unsigned_foo;
drop domain testdomain1;
+
+
+--
+-- Information schema
+--
+
+SELECT * FROM information_schema.column_domain_usage
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+
+SELECT * FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY constraint_name;
+
+SELECT * FROM information_schema.domains
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
+ ORDER BY domain_name;
+
+SELECT * FROM information_schema.check_constraints
+ WHERE (constraint_schema, constraint_name)
+ IN (SELECT constraint_schema, constraint_name
+ FROM information_schema.domain_constraints
+ WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
+ ORDER BY constraint_name;
base-commit: b83033c3cff556d520281aaec399e47b4f11edbe
--
2.43.0
[text/plain] v3-0002-Catalog-domain-not-null-constraints.patch (25.7K, ../../[email protected]/3-v3-0002-Catalog-domain-not-null-constraints.patch)
download | inline diff:
From d34881a605d43241686816aa00603aa1b4a55179 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 6 Feb 2024 14:55:54 +0100
Subject: [PATCH v3 2/2] Catalog domain not-null constraints
This applies the explicit catalog representation of not-null
constraints introduced by b0e96f3119 for table constraints also to
domain not-null constraints.
TODO: catversion
Discussion: https://www.postgresql.org/message-id/flat/9ec24d7b-633d-463a-84c6-7acff769c9e8%40eisentraut.org
---
src/backend/catalog/information_schema.sql | 2 +-
src/backend/catalog/pg_constraint.c | 40 +++
src/backend/commands/typecmds.c | 320 +++++++++++++++------
src/backend/utils/adt/ruleutils.c | 22 +-
src/backend/utils/cache/typcache.c | 2 +-
src/bin/pg_dump/pg_dump.c | 2 +-
src/include/catalog/pg_constraint.h | 1 +
src/test/regress/expected/domain.out | 49 +++-
src/test/regress/sql/domain.sql | 26 ++
9 files changed, 351 insertions(+), 113 deletions(-)
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c402ae72741..76c78c0d184 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -445,7 +445,7 @@ CREATE VIEW check_constraints AS
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
rs.nspname::information_schema.sql_identifier AS constraint_schema,
con.conname::information_schema.sql_identifier AS constraint_name,
- pg_catalog.format('%s IS NOT NULL', at.attname)::information_schema.character_data AS check_clause
+ pg_catalog.format('%s IS NOT NULL', coalesce(at.attname, 'VALUE'))::information_schema.character_data AS check_clause
FROM pg_constraint con
LEFT JOIN pg_namespace rs ON rs.oid = con.connamespace
LEFT JOIN pg_class c ON c.oid = con.conrelid
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0a95608179d..59ca40b7c23 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -631,6 +631,46 @@ findNotNullConstraint(Oid relid, const char *colname)
return findNotNullConstraintAttnum(relid, attnum);
}
+HeapTuple
+findDomainNotNullConstraint(Oid typid)
+{
+ Relation pg_constraint;
+ HeapTuple conTup,
+ retval = NULL;
+ SysScanDesc scan;
+ ScanKeyData key;
+
+ pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(typid));
+ scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId,
+ true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(conTup = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(conTup);
+
+ /*
+ * We're looking for a NOTNULL constraint that's marked validated.
+ */
+ if (con->contype != CONSTRAINT_NOTNULL)
+ continue;
+ if (!con->convalidated)
+ continue;
+
+ /* Found it */
+ retval = heap_copytuple(conTup);
+ break;
+ }
+
+ systable_endscan(scan);
+ table_close(pg_constraint, AccessShareLock);
+
+ return retval;
+}
+
/*
* Given a pg_constraint tuple for a not-null constraint, return the column
* number it is for.
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a400fb39f67..42ee58aab70 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -126,15 +126,19 @@ static Oid findTypeSubscriptingFunction(List *procname, Oid typeOid);
static Oid findRangeSubOpclass(List *opcname, Oid subtype);
static Oid findRangeCanonicalFunction(List *procname, Oid typeOid);
static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype);
-static void validateDomainConstraint(Oid domainoid, char *ccbin);
+static void validateDomainCheckConstraint(Oid domainoid, char *ccbin);
+static void validateDomainNotNullConstraint(Oid domainoid);
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
static void checkEnumOwner(HeapTuple tup);
-static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
- Oid baseTypeOid,
- int typMod, Constraint *constr,
- const char *domainName, ObjectAddress *constrAddr);
+static char *domainAddCheckConstraint(Oid domainOid, Oid domainNamespace,
+ Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr);
static Node *replace_domain_constraint_value(ParseState *pstate,
ColumnRef *cref);
+static void domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr);
static void AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
HeapTuple tup, Relation catalog,
AlterTypeRecurseParams *atparams);
@@ -1105,9 +1109,15 @@ DefineDomain(CreateDomainStmt *stmt)
switch (constr->contype)
{
case CONSTR_CHECK:
- domainAddConstraint(address.objectId, domainNamespace,
- basetypeoid, basetypeMod,
- constr, domainName, NULL);
+ domainAddCheckConstraint(address.objectId, domainNamespace,
+ basetypeoid, basetypeMod,
+ constr, domainName, NULL);
+ break;
+
+ case CONSTR_NOTNULL:
+ domainAddNotNullConstraint(address.objectId, domainNamespace,
+ basetypeoid, basetypeMod,
+ constr, domainName, NULL);
break;
/* Other constraint types were fully processed above */
@@ -2723,66 +2733,32 @@ AlterDomainNotNull(List *names, bool notNull)
return address;
}
- /* Adding a NOT NULL constraint requires checking existing columns */
if (notNull)
{
- List *rels;
- ListCell *rt;
+ Constraint *constr;
- /* Fetch relation list with attributes based on this domain */
- /* ShareLock is sufficient to prevent concurrent data changes */
+ constr = makeNode(Constraint);
+ constr->contype = CONSTR_NOTNULL;
+ constr->initially_valid = true;
+ constr->location = -1;
- rels = get_rels_with_domain(domainoid, ShareLock);
+ domainAddNotNullConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), NULL);
- foreach(rt, rels)
- {
- RelToCheck *rtc = (RelToCheck *) lfirst(rt);
- Relation testrel = rtc->rel;
- TupleDesc tupdesc = RelationGetDescr(testrel);
- TupleTableSlot *slot;
- TableScanDesc scan;
- Snapshot snapshot;
-
- /* Scan all tuples in this relation */
- snapshot = RegisterSnapshot(GetLatestSnapshot());
- scan = table_beginscan(testrel, snapshot, 0, NULL);
- slot = table_slot_create(testrel, NULL);
- while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
- {
- int i;
+ validateDomainNotNullConstraint(domainoid);
+ }
+ else
+ {
+ HeapTuple conTup;
+ ObjectAddress conobj;
- /* Test attributes that are of the domain */
- for (i = 0; i < rtc->natts; i++)
- {
- int attnum = rtc->atts[i];
- Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+ conTup = findDomainNotNullConstraint(domainoid);
+ if (conTup == NULL)
+ elog(ERROR, "could not find not-null constraint on domain \"%s\"", NameStr(typTup->typname));
- if (slot_attisnull(slot, attnum))
- {
- /*
- * In principle the auxiliary information for this
- * error should be errdatatype(), but errtablecol()
- * seems considerably more useful in practice. Since
- * this code only executes in an ALTER DOMAIN command,
- * the client should already know which domain is in
- * question.
- */
- ereport(ERROR,
- (errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("column \"%s\" of table \"%s\" contains null values",
- NameStr(attr->attname),
- RelationGetRelationName(testrel)),
- errtablecol(testrel, attnum)));
- }
- }
- }
- ExecDropSingleTupleTableSlot(slot);
- table_endscan(scan);
- UnregisterSnapshot(snapshot);
-
- /* Close each rel after processing, but keep lock */
- table_close(testrel, NoLock);
- }
+ ObjectAddressSet(conobj, ConstraintRelationId, ((Form_pg_constraint) GETSTRUCT(conTup))->oid);
+ performDeletion(&conobj, DROP_RESTRICT, 0);
}
/*
@@ -2863,10 +2839,17 @@ AlterDomainDropConstraint(List *names, const char *constrName,
/* There can be at most one matching row */
if ((contup = systable_getnext(conscan)) != NULL)
{
+ Form_pg_constraint construct = (Form_pg_constraint) GETSTRUCT(contup);
ObjectAddress conobj;
+ if (construct->contype == CONSTRAINT_NOTNULL)
+ {
+ ((Form_pg_type) GETSTRUCT(tup))->typnotnull = false;
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+ }
+
conobj.classId = ConstraintRelationId;
- conobj.objectId = ((Form_pg_constraint) GETSTRUCT(contup))->oid;
+ conobj.objectId = construct->oid;
conobj.objectSubId = 0;
performDeletion(&conobj, behavior, 0);
@@ -2947,6 +2930,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
switch (constr->contype)
{
case CONSTR_CHECK:
+ case CONSTR_NOTNULL:
/* processed below */
break;
@@ -2989,29 +2973,46 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
break;
}
- /*
- * Since all other constraint types throw errors, this must be a check
- * constraint. First, process the constraint expression and add an entry
- * to pg_constraint.
- */
+ if (constr->contype == CONSTR_CHECK)
+ {
+ /*
+ * First, process the constraint expression and add an entry to
+ * pg_constraint.
+ */
- ccbin = domainAddConstraint(domainoid, typTup->typnamespace,
- typTup->typbasetype, typTup->typtypmod,
- constr, NameStr(typTup->typname), constrAddr);
+ ccbin = domainAddCheckConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), constrAddr);
- /*
- * If requested to validate the constraint, test all values stored in the
- * attributes based on the domain the constraint is being added to.
- */
- if (!constr->skip_validation)
- validateDomainConstraint(domainoid, ccbin);
- /*
- * We must send out an sinval message for the domain, to ensure that any
- * dependent plans get rebuilt. Since this command doesn't change the
- * domain's pg_type row, that won't happen automatically; do it manually.
- */
- CacheInvalidateHeapTuple(typrel, tup, NULL);
+ /*
+ * If requested to validate the constraint, test all values stored in
+ * the attributes based on the domain the constraint is being added
+ * to.
+ */
+ if (!constr->skip_validation)
+ validateDomainCheckConstraint(domainoid, ccbin);
+
+ /*
+ * We must send out an sinval message for the domain, to ensure that
+ * any dependent plans get rebuilt. Since this command doesn't change
+ * the domain's pg_type row, that won't happen automatically; do it
+ * manually.
+ */
+ CacheInvalidateHeapTuple(typrel, tup, NULL);
+ }
+ else if (constr->contype == CONSTR_NOTNULL)
+ {
+ domainAddNotNullConstraint(domainoid, typTup->typnamespace,
+ typTup->typbasetype, typTup->typtypmod,
+ constr, NameStr(typTup->typname), constrAddr);
+
+ if (!constr->skip_validation)
+ validateDomainNotNullConstraint(domainoid);
+
+ typTup->typnotnull = true;
+ CatalogTupleUpdate(typrel, &tup->t_self, tup);
+ }
ObjectAddressSet(address, TypeRelationId, domainoid);
@@ -3096,7 +3097,7 @@ AlterDomainValidateConstraint(List *names, const char *constrName)
val = SysCacheGetAttrNotNull(CONSTROID, tuple, Anum_pg_constraint_conbin);
conbin = TextDatumGetCString(val);
- validateDomainConstraint(domainoid, conbin);
+ validateDomainCheckConstraint(domainoid, conbin);
/*
* Now update the catalog, while we have the door open.
@@ -3123,7 +3124,68 @@ AlterDomainValidateConstraint(List *names, const char *constrName)
}
static void
-validateDomainConstraint(Oid domainoid, char *ccbin)
+validateDomainNotNullConstraint(Oid domainoid)
+{
+ List *rels;
+ ListCell *rt;
+
+ /* Fetch relation list with attributes based on this domain */
+ /* ShareLock is sufficient to prevent concurrent data changes */
+
+ rels = get_rels_with_domain(domainoid, ShareLock);
+
+ foreach(rt, rels)
+ {
+ RelToCheck *rtc = (RelToCheck *) lfirst(rt);
+ Relation testrel = rtc->rel;
+ TupleDesc tupdesc = RelationGetDescr(testrel);
+ TupleTableSlot *slot;
+ TableScanDesc scan;
+ Snapshot snapshot;
+
+ /* Scan all tuples in this relation */
+ snapshot = RegisterSnapshot(GetLatestSnapshot());
+ scan = table_beginscan(testrel, snapshot, 0, NULL);
+ slot = table_slot_create(testrel, NULL);
+ while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
+ {
+ int i;
+
+ /* Test attributes that are of the domain */
+ for (i = 0; i < rtc->natts; i++)
+ {
+ int attnum = rtc->atts[i];
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
+
+ if (slot_attisnull(slot, attnum))
+ {
+ /*
+ * In principle the auxiliary information for this error
+ * should be errdatatype(), but errtablecol() seems
+ * considerably more useful in practice. Since this code
+ * only executes in an ALTER DOMAIN command, the client
+ * should already know which domain is in question.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("column \"%s\" of table \"%s\" contains null values",
+ NameStr(attr->attname),
+ RelationGetRelationName(testrel)),
+ errtablecol(testrel, attnum)));
+ }
+ }
+ }
+ ExecDropSingleTupleTableSlot(slot);
+ table_endscan(scan);
+ UnregisterSnapshot(snapshot);
+
+ /* Close each rel after processing, but keep lock */
+ table_close(testrel, NoLock);
+ }
+}
+
+static void
+validateDomainCheckConstraint(Oid domainoid, char *ccbin)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
@@ -3429,12 +3491,12 @@ checkDomainOwner(HeapTuple tup)
}
/*
- * domainAddConstraint - code shared between CREATE and ALTER DOMAIN
+ * domainAddCheckConstraint - code shared between CREATE and ALTER DOMAIN
*/
static char *
-domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
- int typMod, Constraint *constr,
- const char *domainName, ObjectAddress *constrAddr)
+domainAddCheckConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr)
{
Node *expr;
char *ccbin;
@@ -3442,6 +3504,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
CoerceToDomainValue *domVal;
Oid ccoid;
+ Assert(constr->contype == CONSTR_CHECK);
+
/*
* Assign or validate constraint name
*/
@@ -3562,9 +3626,10 @@ replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
{
/*
* Check for a reference to "value", and if that's what it is, replace
- * with a CoerceToDomainValue as prepared for us by domainAddConstraint.
- * (We handle VALUE as a name, not a keyword, to avoid breaking a lot of
- * applications that have used VALUE as a column name in the past.)
+ * with a CoerceToDomainValue as prepared for us by
+ * domainAddCheckConstraint. (We handle VALUE as a name, not a keyword, to
+ * avoid breaking a lot of applications that have used VALUE as a column
+ * name in the past.)
*/
if (list_length(cref->fields) == 1)
{
@@ -3584,6 +3649,79 @@ replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref)
return NULL;
}
+/*
+ * domainAddNotNullConstraint - code shared between CREATE and ALTER DOMAIN
+ */
+static void
+domainAddNotNullConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
+ int typMod, Constraint *constr,
+ const char *domainName, ObjectAddress *constrAddr)
+{
+ Oid ccoid;
+
+ Assert(constr->contype == CONSTR_NOTNULL);
+
+ /*
+ * Assign or validate constraint name
+ */
+ if (constr->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
+ domainOid,
+ constr->conname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for domain \"%s\" already exists",
+ constr->conname, domainName)));
+ }
+ else
+ constr->conname = ChooseConstraintName(domainName,
+ NULL,
+ "not_null",
+ domainNamespace,
+ NIL);
+
+ /*
+ * Store the constraint in pg_constraint
+ */
+ ccoid =
+ CreateConstraintEntry(constr->conname, /* Constraint Name */
+ domainNamespace, /* namespace */
+ CONSTRAINT_NOTNULL, /* Constraint Type */
+ false, /* Is Deferrable */
+ false, /* Is Deferred */
+ !constr->skip_validation, /* Is Validated */
+ InvalidOid, /* no parent constraint */
+ InvalidOid, /* not a relation constraint */
+ NULL,
+ 0,
+ 0,
+ domainOid, /* domain constraint */
+ InvalidOid, /* no associated index */
+ InvalidOid, /* Foreign key fields */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ NULL,
+ 0,
+ ' ',
+ NULL, /* not an exclusion constraint */
+ NULL,
+ NULL,
+ true, /* is local */
+ 0, /* inhcount */
+ false, /* connoinherit */
+ false, /* conwithoutoverlaps */
+ false); /* is_internal */
+
+ if (constrAddr)
+ ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
+}
+
/*
* Execute ALTER TYPE RENAME
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b625f471a84..e57bbcdbc05 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2494,15 +2494,23 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
}
case CONSTRAINT_NOTNULL:
{
- AttrNumber attnum;
+ if (conForm->conrelid)
+ {
+ AttrNumber attnum;
- attnum = extractNotNullColumn(tup);
+ attnum = extractNotNullColumn(tup);
- appendStringInfo(&buf, "NOT NULL %s",
- quote_identifier(get_attname(conForm->conrelid,
- attnum, false)));
- if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
- appendStringInfoString(&buf, " NO INHERIT");
+ appendStringInfo(&buf, "NOT NULL %s",
+ quote_identifier(get_attname(conForm->conrelid,
+ attnum, false)));
+ if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
+ appendStringInfoString(&buf, " NO INHERIT");
+ }
+ else if (conForm->contypid)
+ {
+ /* conkey is null for domain not-null constraints */
+ appendStringInfoString(&buf, "NOT NULL VALUE");
+ }
break;
}
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 84fc83cb11f..2d689242b65 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -1069,7 +1069,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
Expr *check_expr;
DomainConstraintState *r;
- /* Ignore non-CHECK constraints (presently, shouldn't be any) */
+ /* Ignore non-CHECK constraints */
if (c->contype != CONSTRAINT_CHECK)
continue;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 348748bae53..6261d3e0fb7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7831,7 +7831,7 @@ getDomainConstraints(Archive *fout, TypeInfo *tyinfo)
"pg_catalog.pg_get_constraintdef(oid) AS consrc, "
"convalidated "
"FROM pg_catalog.pg_constraint "
- "WHERE contypid = $1 "
+ "WHERE contypid = $1 AND contype = 'c' "
"ORDER BY conname");
ExecuteSqlStatement(fout, query->data);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 01e6bc21cd1..4e738b50b2e 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -257,6 +257,7 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
extern HeapTuple findNotNullConstraintAttnum(Oid relid, AttrNumber attnum);
extern HeapTuple findNotNullConstraint(Oid relid, const char *colname);
+extern HeapTuple findDomainNotNullConstraint(Oid typid);
extern AttrNumber extractNotNullColumn(HeapTuple constrTup);
extern bool AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
bool is_no_inherit);
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index e70aebd70c0..799b268ac76 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -798,6 +798,29 @@ alter domain con drop constraint nonexistent;
ERROR: constraint "nonexistent" of domain "con" does not exist
alter domain con drop constraint if exists nonexistent;
NOTICE: constraint "nonexistent" of domain "con" does not exist, skipping
+-- not-null constraints
+create domain connotnull integer;
+create table domconnotnulltest
+( col1 connotnull
+, col2 connotnull
+);
+insert into domconnotnulltest default values;
+alter domain connotnull add not null value; -- fails
+ERROR: column "col1" of table "domconnotnulltest" contains null values
+update domconnotnulltest set col1 = 5;
+alter domain connotnull add not null value; -- fails
+ERROR: column "col2" of table "domconnotnulltest" contains null values
+update domconnotnulltest set col2 = 6;
+alter domain connotnull add constraint constr1 not null value;
+update domconnotnulltest set col1 = null; -- fails
+ERROR: domain connotnull does not allow null values
+alter domain connotnull drop constraint constr1;
+update domconnotnulltest set col1 = null;
+drop domain connotnull cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to column col2 of table domconnotnulltest
+drop cascades to column col1 of table domconnotnulltest
+drop table domconnotnulltest;
-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
create domain things AS INT;
CREATE TABLE thethings (stuff things);
@@ -1223,12 +1246,13 @@ SELECT * FROM information_schema.column_domain_usage
SELECT * FROM information_schema.domain_constraints
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
ORDER BY constraint_name;
- constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
---------------------+-------------------+-----------------+----------------+---------------+-------------+---------------+--------------------
- regression | public | con_check | regression | public | con | NO | NO
- regression | public | meow | regression | public | things | NO | NO
- regression | public | pos_int_check | regression | public | pos_int | NO | NO
-(3 rows)
+ constraint_catalog | constraint_schema | constraint_name | domain_catalog | domain_schema | domain_name | is_deferrable | initially_deferred
+--------------------+-------------------+------------------+----------------+---------------+-------------+---------------+--------------------
+ regression | public | con_check | regression | public | con | NO | NO
+ regression | public | meow | regression | public | things | NO | NO
+ regression | public | pos_int_check | regression | public | pos_int | NO | NO
+ regression | public | pos_int_not_null | regression | public | pos_int | NO | NO
+(4 rows)
SELECT * FROM information_schema.domains
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things')
@@ -1247,10 +1271,11 @@ SELECT * FROM information_schema.check_constraints
FROM information_schema.domain_constraints
WHERE domain_name IN ('con', 'dom', 'pos_int', 'things'))
ORDER BY constraint_name;
- constraint_catalog | constraint_schema | constraint_name | check_clause
---------------------+-------------------+-----------------+--------------
- regression | public | con_check | (VALUE > 0)
- regression | public | meow | (VALUE < 11)
- regression | public | pos_int_check | (VALUE > 0)
-(3 rows)
+ constraint_catalog | constraint_schema | constraint_name | check_clause
+--------------------+-------------------+------------------+-------------------
+ regression | public | con_check | (VALUE > 0)
+ regression | public | meow | (VALUE < 11)
+ regression | public | pos_int_check | (VALUE > 0)
+ regression | public | pos_int_not_null | VALUE IS NOT NULL
+(4 rows)
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 813048c19f5..0e71f040040 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -469,6 +469,32 @@
alter domain con drop constraint nonexistent;
alter domain con drop constraint if exists nonexistent;
+-- not-null constraints
+create domain connotnull integer;
+create table domconnotnulltest
+( col1 connotnull
+, col2 connotnull
+);
+
+insert into domconnotnulltest default values;
+alter domain connotnull add not null value; -- fails
+
+update domconnotnulltest set col1 = 5;
+alter domain connotnull add not null value; -- fails
+
+update domconnotnulltest set col2 = 6;
+
+alter domain connotnull add constraint constr1 not null value;
+
+update domconnotnulltest set col1 = null; -- fails
+
+alter domain connotnull drop constraint constr1;
+
+update domconnotnulltest set col1 = null;
+
+drop domain connotnull cascade;
+drop table domconnotnulltest;
+
-- Test ALTER DOMAIN .. CONSTRAINT .. NOT VALID
create domain things AS INT;
CREATE TABLE thethings (stuff things);
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
@ 2024-02-08 12:17 ` jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: jian he @ 2024-02-08 12:17 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
On Wed, Feb 7, 2024 at 4:11 PM Peter Eisentraut <[email protected]> wrote:
>
> >
> > Interesting. I couldn't reproduce this locally, even across different
> > operating systems. The cfbot failures appear to be sporadic, but also
> > happening across multiple systems, so it's clearly not just a local
> > environment failure. Can anyone else perhaps reproduce this locally?
>
> This patch set needed a rebase, so here it is.
>
do you think
add following
ALTER DOMAIN <replaceable class="parameter">name</replaceable> ADD NOT
NULL VALUE
to doc/src/sgml/ref/alter_domain.sgml synopsis makes sense?
otherwise it would be hard to find out this command, i think.
I think I found a bug.
connotnull already set to not null.
every execution of `alter domain connotnull add not null value ;`
would concatenate 'NOT NULL VALUE' for the "Check" column,
That means changes in the function pg_get_constraintdef_worker are not
100% correct.
see below demo:
src8=# \dD+
List of domains
Schema | Name | Type | Collation | Nullable | Default |
Check | Access privileges | Description
--------+------------+---------+-----------+----------+---------+----------------+-------------------+-------------
public | connotnull | integer | | | | NOT
NULL VALUE | |
public | nnint | integer | | not null | | NOT
NULL VALUE | |
(2 rows)
src8=# alter domain connotnull add not null value ;
ALTER DOMAIN
src8=# \dD+
List of domains
Schema | Name | Type | Collation | Nullable | Default |
Check | Access privileges | Descript
ion
--------+------------+---------+-----------+----------+---------+-------------------------------+-------------------+---------
----
public | connotnull | integer | | not null | | NOT
NULL VALUE NOT NULL VALUE | |
public | nnint | integer | | not null | | NOT
NULL VALUE | |
(2 rows)
src8=# alter domain connotnull add not null value ;
ALTER DOMAIN
src8=# \dD+
List of domains
Schema | Name | Type | Collation | Nullable | Default |
Check | Access privil
eges | Description
--------+------------+---------+-----------+----------+---------+----------------------------------------------+--------------
-----+-------------
public | connotnull | integer | | not null | | NOT
NULL VALUE NOT NULL VALUE NOT NULL VALUE |
|
public | nnint | integer | | not null | | NOT
NULL VALUE |
|
(2 rows)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
@ 2024-02-11 21:10 ` Peter Eisentraut <[email protected]>
2024-02-11 21:42 ` Re: Catalog domain not-null constraints Tom Lane <[email protected]>
2024-02-12 10:24 ` Re: Catalog domain not-null constraints Alvaro Herrera <[email protected]>
0 siblings, 2 replies; 18+ messages in thread
From: Peter Eisentraut @ 2024-02-11 21:10 UTC (permalink / raw)
To: jian he <[email protected]>; Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
On 08.02.24 13:17, jian he wrote:
> I think I found a bug.
> connotnull already set to not null.
> every execution of `alter domain connotnull add not null value ;`
> would concatenate 'NOT NULL VALUE' for the "Check" column,
I would have expected that. Each invocation adds a new constraint.
But I see that table constraints do not work that way. A command like
ALTER TABLE t1 ADD NOT NULL c1 does nothing if the column already has a
NOT NULL constraint. I'm not sure this is correct. At least it's not
documented. We should probably make the domains feature work the same
way, but I would like to understand why it works that way first.
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
@ 2024-02-11 21:42 ` Tom Lane <[email protected]>
1 sibling, 0 replies; 18+ messages in thread
From: Tom Lane @ 2024-02-11 21:42 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: jian he <[email protected]>; Alvaro Herrera <[email protected]>; pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
Peter Eisentraut <[email protected]> writes:
> But I see that table constraints do not work that way. A command like
> ALTER TABLE t1 ADD NOT NULL c1 does nothing if the column already has a
> NOT NULL constraint. I'm not sure this is correct. At least it's not
> documented. We should probably make the domains feature work the same
> way, but I would like to understand why it works that way first.
That's probably a hangover from when the underlying state was just
a boolean (attnotnull). Still, I'm a little hesitant to change the
behavior. I do agree that named constraints need to "stack", so
that you'd have to remove each one before not-nullness stops being
enforced. Less sure about unnamed properties.
regards, tom lane
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
@ 2024-02-12 10:24 ` Alvaro Herrera <[email protected]>
2024-02-21 08:01 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-03-14 12:55 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
1 sibling, 2 replies; 18+ messages in thread
From: Alvaro Herrera @ 2024-02-12 10:24 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: jian he <[email protected]>; pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
On 2024-Feb-11, Peter Eisentraut wrote:
> But I see that table constraints do not work that way. A command like ALTER
> TABLE t1 ADD NOT NULL c1 does nothing if the column already has a NOT NULL
> constraint. I'm not sure this is correct. At least it's not documented.
> We should probably make the domains feature work the same way, but I would
> like to understand why it works that way first.
It's an intentional design decision actually; I had it creating multiple
constraints at first, but it had some ugly problems, so I made it behave
this way (which was no small amount of changes). I think the first time
I posted an implementation that worked this way was here
https://postgr.es/m/[email protected]
and then we debated it again later, starting at the bottom of
https://www.postgresql.org/message-id/flat/CAEZATCUA_iPo5kqUun4myghoZtgqbY3jx62%3DGwcYKRMmxFUq_g%40m...
A few messages later, I quoted the SQL standard for DROP NOT NULL, which
is pretty clear that if you run that command, then the column becomes
possibly nullable, which means that we'd have to drop all matching
constraints, or something.
The main source of nastiness, when we allow multiple constraints, is
constraint inheritance. If we allow just one constraint per column,
then it's always easy to know what to do on inheritance attach and
detach: just coninhcount+1 or coninhcount-1 of the one relevant
constraint (which can be matched by column name). If we have multiple
ones, we have to know which one(s) to match and how (by constraint
name?); if the parent has two and the child has one, we need to create
another in the child, with its own coninhcount adjustments; if the
parent has one named parent_col_not_null and the child also has
child_col_not_null, then at ADD INHERIT do we match these ignoring the
differing name, or do we rename the one on child so that we now have
two? Also, the clutter in psql/pg_dump becomes worse.
I would suggest that domain not-null constraints should also allow just
one per column.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"People get annoyed when you try to debug them." (Larry Wall)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-12 10:24 ` Re: Catalog domain not-null constraints Alvaro Herrera <[email protected]>
@ 2024-02-21 08:01 ` jian he <[email protected]>
1 sibling, 0 replies; 18+ messages in thread
From: jian he @ 2024-02-21 08:01 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
wandering around the function AlterDomainNotNull,
the following code can fix the previous undesired behavior.
seems pretty simple, am I missing something?
based on v3-0001-Add-tests-for-domain-related-information-schema-v.patch
and v3-0002-Catalog-domain-not-null-constraints.patch
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 2f94e375..9069465a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2904,7 +2904,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint,
Form_pg_type typTup;
Constraint *constr;
char *ccbin;
- ObjectAddress address;
+ ObjectAddress address = InvalidObjectAddress;
/* Make a TypeName so we can use standard type lookup machinery */
typename = makeTypeNameFromNameList(names);
@@ -3003,6 +3003,12 @@ AlterDomainAddConstraint(List *names, Node
*newConstraint,
}
else if (constr->contype == CONSTR_NOTNULL)
{
+ /* Is the domain already set NOT NULL */
+ if (typTup->typnotnull)
+ {
+ table_close(typrel, RowExclusiveLock);
+ return address;
+ }
domainAddNotNullConstraint(domainoid, typTup->typnamespace,
typTup->typbasetype, typTup->typtypmod,
constr, NameStr(typTup->typname), constrAddr);
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Catalog domain not-null constraints
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-12 10:24 ` Re: Catalog domain not-null constraints Alvaro Herrera <[email protected]>
@ 2024-03-14 12:55 ` Peter Eisentraut <[email protected]>
1 sibling, 0 replies; 18+ messages in thread
From: Peter Eisentraut @ 2024-03-14 12:55 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: jian he <[email protected]>; pgsql-hackers; vignesh C <[email protected]>; Aleksander Alekseev <[email protected]>
On 12.02.24 11:24, Alvaro Herrera wrote:
> On 2024-Feb-11, Peter Eisentraut wrote:
>> But I see that table constraints do not work that way. A command like ALTER
>> TABLE t1 ADD NOT NULL c1 does nothing if the column already has a NOT NULL
>> constraint. I'm not sure this is correct. At least it's not documented.
>> We should probably make the domains feature work the same way, but I would
>> like to understand why it works that way first.
> The main source of nastiness, when we allow multiple constraints, is
> constraint inheritance. If we allow just one constraint per column,
> then it's always easy to know what to do on inheritance attach and
> detach: just coninhcount+1 or coninhcount-1 of the one relevant
> constraint (which can be matched by column name). If we have multiple
> ones, we have to know which one(s) to match and how (by constraint
> name?); if the parent has two and the child has one, we need to create
> another in the child, with its own coninhcount adjustments; if the
> parent has one named parent_col_not_null and the child also has
> child_col_not_null, then at ADD INHERIT do we match these ignoring the
> differing name, or do we rename the one on child so that we now have
> two? Also, the clutter in psql/pg_dump becomes worse.
>
> I would suggest that domain not-null constraints should also allow just
> one per column.
Perhaps it would make sense if we change the ALTER TABLE command to be like
ALTER TABLE t1 ADD IF NOT EXISTS NOT NULL c1
Then the behavior is like one would expect.
For ALTER TABLE, we would reject this command if IF NOT EXISTS is not
specified. (Since this is mainly for pg_dump, it doesn't really matter
for usability.) For ALTER DOMAIN, we could accept both variants.
^ permalink raw reply [nested|flat] 18+ messages in thread
end of thread, other threads:[~2024-03-14 12:55 UTC | newest]
Thread overview: 18+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-31 19:40 [PATCH v24 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v30 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v26 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v22 10/10] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v33 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v27 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v32 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v25 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v28 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v23 10/10] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2020-03-31 19:40 [PATCH v31 09/11] pg_ls_*/pg_stat_file to show file *type*.. Justin Pryzby <[email protected]>
2024-02-07 08:10 Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-08 12:17 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-02-11 21:10 ` Re: Catalog domain not-null constraints Peter Eisentraut <[email protected]>
2024-02-11 21:42 ` Re: Catalog domain not-null constraints Tom Lane <[email protected]>
2024-02-12 10:24 ` Re: Catalog domain not-null constraints Alvaro Herrera <[email protected]>
2024-02-21 08:01 ` Re: Catalog domain not-null constraints jian he <[email protected]>
2024-03-14 12:55 ` Re: Catalog domain not-null constraints Peter Eisentraut <[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