From 0b6b6b438d6a17573f929e50c3adfd726f0b3948 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@otacoo.com>
Date: Wed, 24 Jun 2015 10:37:31 +0900
Subject: [PATCH 3/8] Add IF NOT EXISTS to pg_read_file and pg_read_binary_file

This is useful for code paths that do not want to fail if the file
queried does not exist.
---
 doc/src/sgml/func.sgml           |  16 +++-
 src/backend/commands/extension.c |   2 +-
 src/backend/utils/adt/genfile.c  | 174 +++++++++++++++++++++++++++++++++------
 src/include/catalog/pg_proc.h    |   8 ++
 src/include/utils/builtins.h     |   8 +-
 5 files changed, 179 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0f70985..edfea8b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17816,17 +17816,25 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
       </row>
       <row>
        <entry>
-        <literal><function>pg_read_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</>])</function></literal>
+        <literal><function>pg_read_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</> [, <parameter>if_not_exists</> <type>boolean</>] ])</function></literal>
        </entry>
        <entry><type>text</type></entry>
-       <entry>Return the contents of a text file</entry>
+       <entry>
+        Return the contents of a text file. If
+        <parameter>if_not_exists</parameter> is true, <literal>NULL</literal>
+        is returned if file does not exist instead of an error.
+       </entry>
       </row>
       <row>
        <entry>
-        <literal><function>pg_read_binary_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</>])</function></literal>
+        <literal><function>pg_read_binary_file(<parameter>filename</> <type>text</> [, <parameter>offset</> <type>bigint</>, <parameter>length</> <type>bigint</> [, <parameter>if_not_exists</> <type>boolean</>] ])</function></literal>
        </entry>
        <entry><type>bytea</type></entry>
-       <entry>Return the contents of a file</entry>
+       <entry>
+        Return the contents of a file. If
+        <parameter>if_not_exists</parameter> is true, <literal>NULL</literal>
+        is returned if file does not exist instead of an error.
+       </entry>
       </row>
       <row>
        <entry>
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5cc74d0..25bbe01 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -640,7 +640,7 @@ read_extension_script_file(const ExtensionControlFile *control,
 	char	   *dest_str;
 	int			len;
 
-	content = read_binary_file(filename, 0, -1);
+	content = read_binary_file(filename, 0, -1, false);
 
 	/* use database encoding if not given */
 	if (control->encoding < 0)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index dd97d83..d45c66e 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -88,7 +88,8 @@ convert_and_check_filename(text *arg)
  * We read the whole of the file when bytes_to_read is negative.
  */
 bytea *
-read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
+read_binary_file(const char *filename, int64 seek_offset,
+				 int64 bytes_to_read, bool if_not_exists)
 {
 	bytea	   *buf;
 	size_t		nbytes;
@@ -103,9 +104,14 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
 			struct stat fst;
 
 			if (stat(filename, &fst) < 0)
-				ereport(ERROR,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", filename)));
+			{
+				if (if_not_exists && errno == ENOENT)
+					return NULL;
+				else
+					ereport(ERROR,
+							(errcode_for_file_access(),
+							 errmsg("could not stat file \"%s\": %m", filename)));
+			}
 
 			bytes_to_read = fst.st_size - seek_offset;
 		}
@@ -118,10 +124,15 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
 				 errmsg("requested length too large")));
 
 	if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not open file \"%s\" for reading: %m",
-						filename)));
+	{
+		if (if_not_exists && errno == ENOENT)
+			return NULL;
+		else
+			ereport(ERROR,
+					(errcode_for_file_access(),
+					 errmsg("could not open file \"%s\" for reading: %m",
+							filename)));
+	}
 
 	if (fseeko(file, (off_t) seek_offset,
 			   (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0)
@@ -150,11 +161,16 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
  * in the database encoding.
  */
 static text *
-read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
+read_text_file(const char *filename, int64 seek_offset,
+			   int64 bytes_to_read, bool if_not_exists)
 {
 	bytea	   *buf;
 
-	buf = read_binary_file(filename, seek_offset, bytes_to_read);
+	buf = read_binary_file(filename, seek_offset,
+						   bytes_to_read, if_not_exists);
+
+	if (buf == NULL)
+		return NULL;
 
 	/* Make sure the input is valid */
 	pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false);
@@ -164,21 +180,27 @@ read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read)
 }
 
 /*
- * Read a section of a file, returning it as text
+ * Wrapper function for pg_read_file and pg_read_file_extended.
  */
-Datum
-pg_read_file(PG_FUNCTION_ARGS)
+static Datum
+read_file_wrapper(PG_FUNCTION_ARGS)
 {
 	text	   *filename_t = PG_GETARG_TEXT_P(0);
 	int64		seek_offset = PG_GETARG_INT64(1);
 	int64		bytes_to_read = PG_GETARG_INT64(2);
+	bool		if_not_exists = false;
 	char	   *filename;
+	bytea	   *result;
 
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser to read files"))));
 
+	/* Check for option if_not_exists */
+	if (PG_NARGS() == 4 && !PG_ARGISNULL(3))
+		if_not_exists = PG_GETARG_BOOL(3);
+
 	filename = convert_and_check_filename(filename_t);
 
 	if (bytes_to_read < 0)
@@ -186,44 +208,100 @@ pg_read_file(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("requested length cannot be negative")));
 
-	PG_RETURN_TEXT_P(read_text_file(filename, seek_offset, bytes_to_read));
+	result = read_text_file(filename, seek_offset, bytes_to_read,
+							if_not_exists);
+	if (result == NULL)
+		PG_RETURN_NULL();
+	PG_RETURN_TEXT_P(result);
 }
 
 /*
- * Read the whole of a file, returning it as text
+ * Read a section of a file, returning it as text
  */
 Datum
-pg_read_file_all(PG_FUNCTION_ARGS)
+pg_read_file(PG_FUNCTION_ARGS)
+{
+	return read_file_wrapper(fcinfo);
+}
+
+/*
+ * Similar to pg_read_file, with IF NOT EXISTS option.
+ */
+Datum
+pg_read_file_extended(PG_FUNCTION_ARGS)
+{
+	return read_file_wrapper(fcinfo);
+}
+
+/*
+ * Wrapper function for pg_read_file_all and pg_read_file_all_extended.
+ */
+static Datum
+read_file_all_wrapper(PG_FUNCTION_ARGS)
 {
 	text	   *filename_t = PG_GETARG_TEXT_P(0);
 	char	   *filename;
+	text	   *result;
+	bool		if_not_exists = false;
 
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser to read files"))));
 
+	/* Check for option if_not_exists */
+	if (PG_NARGS() == 2 && !PG_ARGISNULL(1))
+		if_not_exists = PG_GETARG_BOOL(1);
+
 	filename = convert_and_check_filename(filename_t);
 
-	PG_RETURN_TEXT_P(read_text_file(filename, 0, -1));
+	result = read_text_file(filename, 0, -1, if_not_exists);
+	if (result == NULL)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(result);
 }
 
 /*
- * Read a section of a file, returning it as bytea
+ * Read the whole of a file, returning it as text
  */
 Datum
-pg_read_binary_file(PG_FUNCTION_ARGS)
+pg_read_file_all(PG_FUNCTION_ARGS)
+{
+	return read_file_all_wrapper(fcinfo);
+}
+
+/*
+ * Similar to pg_read_file_all, with IF NOT EXISTS option.
+ */
+Datum
+pg_read_file_all_extended(PG_FUNCTION_ARGS)
+{
+	return read_file_all_wrapper(fcinfo);
+}
+
+/*
+ * Wrapper for pg_read_binary_file and pg_read_binary_file_extended.
+ */
+static Datum
+read_binary_file_wrapper(PG_FUNCTION_ARGS)
 {
 	text	   *filename_t = PG_GETARG_TEXT_P(0);
 	int64		seek_offset = PG_GETARG_INT64(1);
 	int64		bytes_to_read = PG_GETARG_INT64(2);
 	char	   *filename;
+	bool		if_not_exists = false;
+	bytea	   *result;
 
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser to read files"))));
 
+	/* Check for option if_not_exists */
+	if (PG_NARGS() == 4 && !PG_ARGISNULL(3))
+		if_not_exists = PG_GETARG_BOOL(3);
+
 	filename = convert_and_check_filename(filename_t);
 
 	if (bytes_to_read < 0)
@@ -231,26 +309,76 @@ pg_read_binary_file(PG_FUNCTION_ARGS)
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("requested length cannot be negative")));
 
-	PG_RETURN_BYTEA_P(read_binary_file(filename, seek_offset, bytes_to_read));
+	result = read_binary_file(filename, seek_offset,
+							  bytes_to_read, if_not_exists);
+	if (result == NULL)
+		PG_RETURN_NULL();
+	PG_RETURN_BYTEA_P(result);
 }
 
 /*
- * Read the whole of a file, returning it as bytea
+ * Read a section of a file, returning it as bytea
  */
 Datum
-pg_read_binary_file_all(PG_FUNCTION_ARGS)
+pg_read_binary_file(PG_FUNCTION_ARGS)
+{
+	return read_binary_file_wrapper(fcinfo);
+}
+
+/*
+ * Similar to pg_read_binary_file, with IF NOT EXISTS option.
+ */
+Datum
+pg_read_binary_file_extended(PG_FUNCTION_ARGS)
+{
+	return read_binary_file_wrapper(fcinfo);
+}
+
+/*
+ * Wrapper for pg_read_binary_file_all and pg_read_binary_file_all_extended.
+ */
+static Datum
+read_binary_file_all_wrapper(PG_FUNCTION_ARGS)
 {
 	text	   *filename_t = PG_GETARG_TEXT_P(0);
 	char	   *filename;
+	bool		if_not_exists = false;
+	text	   *result;
 
 	if (!superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 (errmsg("must be superuser to read files"))));
 
+	/* Check for option if_not_exists */
+	if (PG_NARGS() == 2 && !PG_ARGISNULL(1))
+		if_not_exists = PG_GETARG_BOOL(1);
+
 	filename = convert_and_check_filename(filename_t);
 
-	PG_RETURN_BYTEA_P(read_binary_file(filename, 0, -1));
+	result = read_binary_file(filename, 0, -1, if_not_exists);
+	if (result == NULL)
+		PG_RETURN_NULL();
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * Read the whole of a file, returning it as bytea
+ */
+Datum
+pg_read_binary_file_all(PG_FUNCTION_ARGS)
+{
+	return read_binary_file_all_wrapper(fcinfo);
+}
+
+/*
+ * Similar to pg_read_binary_file_all, with IF NOT EXISTS option.
+ */
+Datum
+pg_read_binary_file_all_extended(PG_FUNCTION_ARGS)
+{
+	return read_binary_file_all_wrapper(fcinfo);
 }
 
 /*
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8da1781..c805a9a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3189,12 +3189,20 @@ DATA(insert OID = 3307 ( pg_stat_file		PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0
 DESCR("get information about file");
 DATA(insert OID = 2624 ( pg_read_file		PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
 DESCR("read text from a file");
+DATA(insert OID = 3293 ( pg_read_file		PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 25 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_file_extended _null_ _null_ _null_ ));
+DESCR("read text from a file");
 DATA(insert OID = 3826 ( pg_read_file		PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_read_file_all _null_ _null_ _null_ ));
 DESCR("read text from a file");
+DATA(insert OID = 3294 ( pg_read_file		PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 25 "25 16" _null_ _null_ _null_ _null_ _null_ pg_read_file_all_extended _null_ _null_ _null_ ));
+DESCR("read text from a file");
 DATA(insert OID = 3827 ( pg_read_binary_file	PGNSP PGUID 12 1 0 0 0 f f f f t f v 3 0 17 "25 20 20" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file _null_ _null_ _null_ ));
 DESCR("read bytea from a file");
+DATA(insert OID = 3295 ( pg_read_binary_file	PGNSP PGUID 12 1 0 0 0 f f f f t f v 4 0 17 "25 20 20 16" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_extended _null_ _null_ _null_ ));
+DESCR("read bytea from a file");
 DATA(insert OID = 3828 ( pg_read_binary_file	PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 17 "25" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_all _null_ _null_ _null_ ));
 DESCR("read bytea from a file");
+DATA(insert OID = 3296 ( pg_read_binary_file	PGNSP PGUID 12 1 0 0 0 f f f f t f v 2 0 17 "25 16" _null_ _null_ _null_ _null_ _null_ pg_read_binary_file_all_extended _null_ _null_ _null_ ));
+DESCR("read bytea from a file");
 DATA(insert OID = 2625 ( pg_ls_dir			PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ ));
 DESCR("list all files in a directory");
 DATA(insert OID = 2626 ( pg_sleep			PGNSP PGUID 12 1 0 0 0 f f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index e46650a..82d0e18 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -470,13 +470,19 @@ extern Datum pg_relation_filepath(PG_FUNCTION_ARGS);
 
 /* genfile.c */
 extern bytea *read_binary_file(const char *filename,
-				 int64 seek_offset, int64 bytes_to_read);
+							   int64 seek_offset,
+							   int64 bytes_to_read,
+							   bool if_not_exists);
 extern Datum pg_stat_file(PG_FUNCTION_ARGS);
 extern Datum pg_stat_file_extended(PG_FUNCTION_ARGS);
 extern Datum pg_read_file(PG_FUNCTION_ARGS);
+extern Datum pg_read_file_extended(PG_FUNCTION_ARGS);
 extern Datum pg_read_file_all(PG_FUNCTION_ARGS);
+extern Datum pg_read_file_all_extended(PG_FUNCTION_ARGS);
 extern Datum pg_read_binary_file(PG_FUNCTION_ARGS);
+extern Datum pg_read_binary_file_extended(PG_FUNCTION_ARGS);
 extern Datum pg_read_binary_file_all(PG_FUNCTION_ARGS);
+extern Datum pg_read_binary_file_all_extended(PG_FUNCTION_ARGS);
 extern Datum pg_ls_dir(PG_FUNCTION_ARGS);
 
 /* misc.c */
-- 
2.4.4

