From 5c0b6418971799e3d0b76a6ce55bff91a73fa371 Mon Sep 17 00:00:00 2001
From: David Christensen <david@endpoint.com>
Date: Fri, 20 Mar 2015 12:03:47 -0500
Subject: [PATCH] Add two-arg form of current_setting() to optionally suppress
 errors

The current_setting() function by default throws an error if the GUC
is unset/unknown.  Add a second argument "missing_ok" which will
return NULL when an unset GUC is encountered and the parameter is
true.

Since GUCs cannot be set to NULL, this can be used to check for the
existance of a particular GUC without throwing an error by testing
whether the two-arg form returns NULL or not.

This can also be combined with COALESCE() to provide support for a
fallback/default value instead of throwing an error when an unknown
GUC is encountered.  This would come in most useful when using custom
GUCs; e.g.:

  -- errors out if the 'foo.bar' setting is unset
  SELECT current_setting('foo.bar');

  -- returns current setting of foo.bar, or NULL if not set
  SELECT current_setting('foo.bar', 'true')

  -- returns current setting of foo.bar, or 'default' if not set
  SELECT COALESCE(current_setting('foo.bar', 'true'), 'default')

This saves you having to wrap the use of the function in an exception
block just to catch and handle a default setting within a function.
---
 doc/src/sgml/func.sgml            |  6 ++++--
 src/backend/utils/misc/guc.c      | 43 ++++++++++++++++++++++++++++++++++++---
 src/include/catalog/pg_proc.h     |  2 ++
 src/include/utils/builtins.h      |  1 +
 src/include/utils/guc.h           |  4 +++-
 src/test/regress/expected/guc.out | 36 ++++++++++++++++++++++++++++++++
 src/test/regress/sql/guc.sql      | 22 ++++++++++++++++++++
 7 files changed, 108 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index aa19e10..d36a0f1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -16215,7 +16215,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <indexterm>
          <primary>current_setting</primary>
         </indexterm>
-        <literal><function>current_setting(<parameter>setting_name</parameter>)</function></literal>
+        <literal><function>current_setting(<parameter>setting_name</parameter> [, <parameter>missing_ok</parameter> ])</function></literal>
        </entry>
        <entry><type>text</type></entry>
        <entry>get current value of setting</entry>
@@ -16254,7 +16254,9 @@ SELECT collation for ('foo' COLLATE "de_DE");
     The function <function>current_setting</function> yields the
     current value of the setting <parameter>setting_name</parameter>.
     It corresponds to the <acronym>SQL</acronym> command
-    <command>SHOW</command>.  An example:
+    <command>SHOW</command>.  If <parameter>setting</parameter> does not exist, throws an error
+    unless <parameter>missing_ok</parameter> is <literal>true</literal>.  An
+    example:
 <programlisting>
 SELECT current_setting('datestyle');
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 26275bd..5245ee8 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -7696,20 +7696,27 @@ ShowAllGUCConfig(DestReceiver *dest)
 	end_tup_output(tstate);
 }
 
+
 /*
- * Return GUC variable value by name; optionally return canonical
- * form of name.  Return value is palloc'd.
+ * Return GUC variable value by name; optionally return canonical form of
+ * name.  If the GUC is unset, then throw an error unless missing_ok is true,
+ * in which case return NULL.  Return value is palloc'd.
  */
 char *
-GetConfigOptionByName(const char *name, const char **varname)
+GetConfigOptionByNameMissingOK(const char *name, const char **varname, bool missing_ok)
 {
 	struct config_generic *record;
 
 	record = find_option(name, false, ERROR);
 	if (record == NULL)
+	{
+		if (missing_ok)
+			return NULL;
+
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 			   errmsg("unrecognized configuration parameter \"%s\"", name)));
+	}
 	if ((record->flags & GUC_SUPERUSER_ONLY) && !superuser())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -8008,6 +8015,36 @@ show_config_by_name(PG_FUNCTION_ARGS)
 }
 
 /*
+ * show_config_by_name_missing_ok - equiv to SHOW X command but implemented as
+ * a function.  If X does not exist, suppress the error and just return NULL
+ * if missing_ok is TRUE.
+ */
+Datum
+show_config_by_name_missing_ok(PG_FUNCTION_ARGS)
+{
+	char	   *varname;
+	bool       missing_ok;
+	char	   *varval;
+
+	/* Get the GUC variable name */
+	varname = TextDatumGetCString(PG_GETARG_DATUM(0));
+
+	/* Get the missing_ok value */
+	missing_ok = PG_GETARG_BOOL(1);
+
+	/* Get the value */
+	varval = GetConfigOptionByNameMissingOK(varname, NULL, missing_ok);
+
+	/* return NULL if this was NULL */
+	if (!varval)
+		PG_RETURN_NULL();
+
+	/* Convert to text */
+	PG_RETURN_TEXT_P(cstring_to_text(varval));
+}
+
+
+/*
  * show_all_settings - equiv to SHOW ALL command but implemented as
  * a Table Function.
  */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3c218a3..d1092a0 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3040,6 +3040,8 @@ DESCR("convert bitstring to int8");
 
 DATA(insert OID = 2077 (  current_setting	PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 25 "25" _null_ _null_ _null_ _null_ show_config_by_name _null_ _null_ _null_ ));
 DESCR("SHOW X as a function");
+DATA(insert OID = 3280 (  current_setting	PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 25 "25 16" _null_ _null_ _null_ _null_ show_config_by_name_missing_ok _null_ _null_ _null_ ));
+DESCR("SHOW X as a function; if second param is true then don't thrown an error if unset");
 DATA(insert OID = 2078 (  set_config		PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 25 "25 25 16" _null_ _null_ _null_ _null_ set_config_by_name _null_ _null_ _null_ ));
 DESCR("SET X as a function");
 DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline}" _null_ show_all_settings _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 6310641..4d672ae 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1095,6 +1095,7 @@ extern Datum quote_nullable(PG_FUNCTION_ARGS);
 
 /* guc.c */
 extern Datum show_config_by_name(PG_FUNCTION_ARGS);
+extern Datum show_config_by_name_fallback(PG_FUNCTION_ARGS);
 extern Datum set_config_by_name(PG_FUNCTION_ARGS);
 extern Datum show_all_settings(PG_FUNCTION_ARGS);
 
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index d3100d1..3f294f1 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -351,9 +351,11 @@ extern int set_config_option(const char *name, const char *value,
 				  GucAction action, bool changeVal, int elevel,
 				  bool is_reload);
 extern void AlterSystemSetConfigFile(AlterSystemStmt *setstmt);
-extern char *GetConfigOptionByName(const char *name, const char **varname);
+
+extern char *GetConfigOptionByNameMissingOK(const char *name, const char **varname, bool missing_ok);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
 extern int	GetNumConfigOptions(void);
+#define GetConfigOptionByName(name,var) GetConfigOptionByNameMissingOK(name,var,false);
 
 extern void SetPGVariable(const char *name, List *args, bool is_local);
 extern void GetPGVariable(const char *name, DestReceiver *dest);
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 4f0065c..e20f6a9 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -736,3 +736,39 @@ NOTICE:  text search configuration "no_such_config" does not exist
 select func_with_bad_set();
 ERROR:  invalid value for parameter "default_text_search_config": "no_such_config"
 reset check_function_bodies;
+-- check multi-arg custom current_setting
+-- this should error:
+select current_setting('nosuch.setting');
+ERROR:  unrecognized configuration parameter "nosuch.setting"
+-- this should also error
+select current_setting('nosuch.setting',false);
+ERROR:  unrecognized configuration parameter "nosuch.setting"
+-- this should return NULL
+select current_setting('nosuch.setting',true) is null;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- this should now return 'nada'
+set nosuch.setting = 'nada';
+select current_setting('nosuch.setting');
+ current_setting 
+-----------------
+ nada
+(1 row)
+
+-- as should this
+select current_setting('nosuch.setting',false);
+ current_setting 
+-----------------
+ nada
+(1 row)
+
+-- as should this
+select current_setting('nosuch.setting',true);
+ current_setting 
+-----------------
+ nada
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index 3de8a6b..a1561f2 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -275,3 +275,25 @@ set default_text_search_config = no_such_config;
 select func_with_bad_set();
 
 reset check_function_bodies;
+
+-- check multi-arg custom current_setting
+
+-- this should error:
+select current_setting('nosuch.setting');
+
+-- this should also error
+select current_setting('nosuch.setting',false);
+
+-- this should return NULL
+select current_setting('nosuch.setting',true) is null;
+
+-- this should now return 'nada'
+set nosuch.setting = 'nada';
+
+select current_setting('nosuch.setting');
+
+-- as should this
+select current_setting('nosuch.setting',false);
+
+-- as should this
+select current_setting('nosuch.setting',true);
-- 
2.3.3

