From 662abcc9c3c312955577e02f521576ac8949152f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 20 Nov 2022 03:29:09 +0300
Subject: [PATCH] ALTER ROLE ... SET ... TO ... USER SET

---
 doc/src/sgml/ref/alter_database.sgml     |  15 ++-
 doc/src/sgml/ref/alter_role.sgml         |  22 ++++-
 src/backend/catalog/pg_db_role_setting.c |   4 +-
 src/backend/commands/functioncmds.c      |   2 +-
 src/backend/parser/gram.y                |  20 ++++
 src/backend/utils/misc/guc.c             | 111 ++++++++++++++++-------
 src/backend/utils/misc/guc_funcs.c       |  12 ++-
 src/bin/pg_dump/dumputils.c              |  18 +++-
 src/include/common/guc-common.h          |  31 +++++++
 src/include/nodes/parsenodes.h           |   1 +
 src/include/utils/guc.h                  |   4 +-
 11 files changed, 198 insertions(+), 42 deletions(-)
 create mode 100644 src/include/common/guc-common.h

diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..697db7b720f 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
 
 ALTER DATABASE <replaceable class="parameter">name</replaceable> REFRESH COLLATION VERSION
 
-ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | DEFAULT }
+ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | <replaceable>value</replaceable> USER SET | DEFAULT }
 ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> FROM CURRENT
 ALTER DATABASE <replaceable class="parameter">name</replaceable> RESET <replaceable>configuration_parameter</replaceable>
 ALTER DATABASE <replaceable class="parameter">name</replaceable> RESET ALL
@@ -206,6 +206,19 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> RESET ALL
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><literal>USER SET</literal></term>
+      <listitem>
+       <para>
+        Specifies that variable should be set on behalf of ordinal role.
+        That lets ordinal role set placeholder variables, which permission
+        requirements is not known yet; see <xref linkend="runtime-config-custom"/>.
+        The value set wouldn't be used if variable finally appear to require
+        superuser privileges.
+       </para>
+      </listitem>
+     </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..0c51d1d0561 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
 
 ALTER ROLE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
 
-ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | DEFAULT }
+ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | <replaceable>value</replaceable> USER SET | DEFAULT }
 ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> FROM CURRENT
 ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET <replaceable>configuration_parameter</replaceable>
 ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET ALL
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><literal>USER SET</literal></term>
+      <listitem>
+       <para>
+        Specifies that variable should be set on behalf of ordinal role.
+        That lets ordinal role set placeholder variables, which permission
+        requirements is not known yet; see <xref linkend="runtime-config-custom"/>.
+        The value set wouldn't be used if variable finally appear to require
+        superuser privileges.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
  </refsect1>
 
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
 
 <programlisting>
 ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG;
+</programlisting></para>
+
+  <para>
+   Give a role a non-default placeholder setting on behalf of ordinal user.
+
+<programlisting>
+ALTER ROLE fred SET my.param = 'value' USER SET;
 </programlisting></para>
  </refsect1>
 
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 42387f4e304..4b9a39a953d 100644
--- a/src/backend/catalog/pg_db_role_setting.c
+++ b/src/backend/catalog/pg_db_role_setting.c
@@ -115,7 +115,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 
 		/* Update (valuestr is NULL in RESET cases) */
 		if (valuestr)
-			a = GUCArrayAdd(a, setstmt->name, valuestr);
+			a = GUCArrayAdd(a, setstmt->name, valuestr, setstmt->user_set);
 		else
 			a = GUCArrayDelete(a, setstmt->name);
 
@@ -141,7 +141,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt)
 
 		memset(nulls, false, sizeof(nulls));
 
-		a = GUCArrayAdd(NULL, setstmt->name, valuestr);
+		a = GUCArrayAdd(NULL, setstmt->name, valuestr, setstmt->user_set);
 
 		values[Anum_pg_db_role_setting_setdatabase - 1] =
 			ObjectIdGetDatum(databaseid);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 1f820c93e96..950fd28badc 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -662,7 +662,7 @@ update_proconfig_value(ArrayType *a, List *set_items)
 			char	   *valuestr = ExtractSetVariableArgs(sstmt);
 
 			if (valuestr)
-				a = GUCArrayAdd(a, sstmt->name, valuestr);
+				a = GUCArrayAdd(a, sstmt->name, valuestr, sstmt->user_set);
 			else				/* RESET */
 				a = GUCArrayDelete(a, sstmt->name);
 		}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 737bd2d06d5..c38398d8528 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1622,6 +1622,26 @@ generic_set:
 					n->args = $3;
 					$$ = n;
 				}
+			| var_name TO var_list USER SET
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+
+					n->kind = VAR_SET_VALUE;
+					n->name = $1;
+					n->args = $3;
+					n->user_set = true;
+					$$ = n;
+				}
+			| var_name '=' var_list USER SET
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+
+					n->kind = VAR_SET_VALUE;
+					n->name = $1;
+					n->args = $3;
+					n->user_set = true;
+					$$ = n;
+				}
 			| var_name TO DEFAULT
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 117a2d26a0e..b311142ff2f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -224,7 +224,6 @@ static bool reporting_enabled;	/* true to enable GUC_REPORT */
 
 static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
-
 static int	guc_var_compare(const void *a, const void *b);
 static uint32 guc_name_hash(const void *key, Size keysize);
 static int	guc_name_match(const void *key1, const void *key2, Size keysize);
@@ -244,7 +243,7 @@ static void reapply_stacked_values(struct config_generic *variable,
 								   GucContext curscontext, GucSource cursource,
 								   Oid cursrole);
 static bool validate_option_array_item(const char *name, const char *value,
-									   bool skipIfNoPermissions);
+									   bool user_set, bool skipIfNoPermissions);
 static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head);
 static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
 									  const char *name, const char *value);
@@ -6159,13 +6158,16 @@ RestoreGUCState(void *gucstate)
 
 /*
  * A little "long argument" simulation, although not quite GNU
- * compliant. Takes a string of the form "some-option=some value" and
- * returns name = "some_option" and value = "some value" in palloc'ed
- * storage. Note that '-' is converted to '_' in the option name. If
- * there is no '=' in the input string then value will be NULL.
+ * compliant. Takes a string of the form "some-option=some value" or
+ * "some-option(u)=some value" and returns name = "some_option" and
+ * value = "some value" in palloc'ed storage. If user_set is not null then
+ * the presence of "(u)" flag is stored there. Note that '-' is converted
+ * to '_' in the option name. If there is no '=' in the input string then
+ * value will be NULL.
  */
-void
-ParseLongOption(const char *string, char **name, char **value)
+static void
+ParseLongOptionInternal(const char *string, char **name, char **value,
+						bool *user_set)
 {
 	size_t		equal_pos;
 	char	   *cp;
@@ -6176,25 +6178,41 @@ ParseLongOption(const char *string, char **name, char **value)
 
 	equal_pos = strcspn(string, "=");
 
-	if (string[equal_pos] == '=')
+	if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(string, string + equal_pos))
+	{
+		*name = palloc(equal_pos - GUC_ARRAY_USERSET_SIGN_LEN + 1);
+		strlcpy(*name, string, equal_pos - GUC_ARRAY_USERSET_SIGN_LEN + 1);
+		if (user_set)
+			*user_set = true;
+	}
+	else
 	{
 		*name = palloc(equal_pos + 1);
 		strlcpy(*name, string, equal_pos + 1);
+		if (user_set)
+			*user_set = false;
+	}
 
+	if (string[equal_pos] == '=')
 		*value = pstrdup(&string[equal_pos + 1]);
-	}
 	else
-	{
-		/* no equal sign in string */
-		*name = pstrdup(string);
 		*value = NULL;
-	}
 
 	for (cp = *name; *cp; cp++)
 		if (*cp == '-')
 			*cp = '_';
 }
 
+/*
+ * The exported version of ParseLongOptionInternal().  Doesn't need user_set
+ * argument since no external users need it.
+ */
+void
+ParseLongOption(const char *string, char **name, char **value)
+{
+	ParseLongOptionInternal(string, name, value, NULL);
+}
+
 
 /*
  * Handle options fetched from pg_db_role_setting.setconfig,
@@ -6220,6 +6238,7 @@ ProcessGUCArray(ArrayType *array,
 		char	   *s;
 		char	   *name;
 		char	   *value;
+		bool		user_set;
 
 		d = array_ref(array, 1, &i,
 					  -1 /* varlenarray */ ,
@@ -6233,7 +6252,7 @@ ProcessGUCArray(ArrayType *array,
 
 		s = TextDatumGetCString(d);
 
-		ParseLongOption(s, &name, &value);
+		ParseLongOptionInternal(s, &name, &value, &user_set);
 		if (!value)
 		{
 			ereport(WARNING,
@@ -6245,7 +6264,7 @@ ProcessGUCArray(ArrayType *array,
 		}
 
 		(void) set_config_option(name, value,
-								 context, source,
+								 user_set ? PGC_USERSET : context, source,
 								 action, true, 0, false);
 
 		pfree(name);
@@ -6260,7 +6279,8 @@ ProcessGUCArray(ArrayType *array,
  * to indicate the current table entry is NULL.
  */
 ArrayType *
-GUCArrayAdd(ArrayType *array, const char *name, const char *value)
+GUCArrayAdd(ArrayType *array, const char *name, const char *value,
+			bool user_set)
 {
 	struct config_generic *record;
 	Datum		datum;
@@ -6271,7 +6291,7 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
 	Assert(value);
 
 	/* test if the option is valid and we're allowed to set it */
-	(void) validate_option_array_item(name, value, false);
+	(void) validate_option_array_item(name, value, user_set, false);
 
 	/* normalize name (converts obsolete GUC names to modern spellings) */
 	record = find_option(name, false, true, WARNING);
@@ -6279,7 +6299,11 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
 		name = record->name;
 
 	/* build new item for array */
-	newval = psprintf("%s=%s", name, value);
+	if (user_set)
+		newval = psprintf("%s" GUC_ARRAY_USERSET_SIGN "=%s",
+						  name, value);
+	else
+		newval = psprintf("%s=%s", name, value);
 	datum = CStringGetTextDatum(newval);
 
 	if (array)
@@ -6309,9 +6333,17 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
 				continue;
 			current = TextDatumGetCString(d);
 
-			/* check for match up through and including '=' */
-			if (strncmp(current, newval, strlen(name) + 1) == 0)
+			/* check for the name match */
+			if (strncmp(current, newval, strlen(name)) == 0 &&
+				GUC_ARRAY_IS_NAME_BORDER(current + strlen(name)))
 			{
+				/*
+				 * Recheck permissons if we found an option without USER SET
+				 * flag while we're setting an optionn with USER SET flag.
+				 */
+				if (current[strlen(name)] == '=' && user_set)
+					(void) validate_option_array_item(name, value,
+													  false, false);
 				index = i;
 				break;
 			}
@@ -6347,9 +6379,6 @@ GUCArrayDelete(ArrayType *array, const char *name)
 
 	Assert(name);
 
-	/* test if the option is valid and we're allowed to set it */
-	(void) validate_option_array_item(name, NULL, false);
-
 	/* normalize name (converts obsolete GUC names to modern spellings) */
 	record = find_option(name, false, true, WARNING);
 	if (record)
@@ -6379,9 +6408,15 @@ GUCArrayDelete(ArrayType *array, const char *name)
 		val = TextDatumGetCString(d);
 
 		/* ignore entry if it's what we want to delete */
-		if (strncmp(val, name, strlen(name)) == 0
-			&& val[strlen(name)] == '=')
+		if (strncmp(val, name, strlen(name)) == 0 &&
+			GUC_ARRAY_IS_NAME_BORDER(val + strlen(name)))
+		{
+			/* test if the option is valid and we're allowed to set it */
+			(void) validate_option_array_item(name, NULL,
+											  GUC_ARRAY_IS_USERSET_SIGN(val + strlen(name)),
+											  false);
 			continue;
+		}
 
 		/* else add it to the output array */
 		if (newarray)
@@ -6431,6 +6466,7 @@ GUCArrayReset(ArrayType *array)
 		char	   *val;
 		char	   *eqsgn;
 		bool		isnull;
+		bool		user_set = false;
 
 		d = array_ref(array, 1, &i,
 					  -1 /* varlenarray */ ,
@@ -6443,10 +6479,18 @@ GUCArrayReset(ArrayType *array)
 		val = TextDatumGetCString(d);
 
 		eqsgn = strchr(val, '=');
-		*eqsgn = '\0';
+		if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(val, eqsgn))
+		{
+			*(eqsgn - GUC_ARRAY_USERSET_SIGN_LEN) = '\0';
+			user_set = true;
+		}
+		else
+		{
+			*eqsgn = '\0';
+		}
 
 		/* skip if we have permission to delete it */
-		if (validate_option_array_item(val, NULL, true))
+		if (validate_option_array_item(val, NULL, user_set, true))
 			continue;
 
 		/* else add it to the output array */
@@ -6472,15 +6516,16 @@ GUCArrayReset(ArrayType *array)
  * Validate a proposed option setting for GUCArrayAdd/Delete/Reset.
  *
  * name is the option name.  value is the proposed value for the Add case,
- * or NULL for the Delete/Reset cases.  If skipIfNoPermissions is true, it's
- * not an error to have no permissions to set the option.
+ * or NULL for the Delete/Reset cases.  user_set indicates this is the USER SET
+ * option.  If skipIfNoPermissions is true, it's not an error to have no
+ * permissions to set the option.
  *
  * Returns true if OK, false if skipIfNoPermissions is true and user does not
  * have permission to change this option (all other error cases result in an
  * error being thrown).
  */
 static bool
-validate_option_array_item(const char *name, const char *value,
+validate_option_array_item(const char *name, const char *value, bool user_set,
 						   bool skipIfNoPermissions)
 
 {
@@ -6516,8 +6561,10 @@ validate_option_array_item(const char *name, const char *value,
 	{
 		/*
 		 * We cannot do any meaningful check on the value, so only permissions
-		 * are useful to check.
+		 * are useful to check.  USER SET options are always allowed.
 		 */
+		if (user_set)
+			return true;
 		if (superuser() ||
 			pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)
 			return true;
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index 108b3bd1290..963921710cd 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -166,12 +166,22 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 char *
 ExtractSetVariableArgs(VariableSetStmt *stmt)
 {
+
 	switch (stmt->kind)
 	{
 		case VAR_SET_VALUE:
 			return flatten_set_variable_args(stmt->name, stmt->args);
 		case VAR_SET_CURRENT:
-			return GetConfigOptionByName(stmt->name, NULL, false);
+		{
+			struct config_generic *record;
+			char	   *result;
+
+			result = GetConfigOptionByName(stmt->name, NULL, false);
+			record = find_option(stmt->name, false, false, ERROR);
+			stmt->user_set = (record->scontext == PGC_USERSET);
+
+			return result;
+		}
 		default:
 			return NULL;
 	}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6e501a54138..4316232d1d1 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -16,6 +16,7 @@
 
 #include <ctype.h>
 
+#include "common/guc-common.h"
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
 
@@ -804,8 +805,8 @@ SplitGUCList(char *rawstring, char separator,
 /*
  * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands.
  *
- * Parse the contents of configitem (a "name=value" string), wrap it in
- * a complete ALTER command, and append it to buf.
+ * Parse the contents of configitem (a "name=value" or "name(u)=value" string),
+ * wrap it in a complete ALTER command, and append it to buf.
  *
  * type is DATABASE or ROLE, and name is the name of the database or role.
  * If we need an "IN" clause, type2 and name2 similarly define what to put
@@ -820,6 +821,7 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
 {
 	char	   *mine;
 	char	   *pos;
+	bool		user_set = false;
 
 	/* Parse the configitem.  If we can't find an "=", silently do nothing. */
 	mine = pg_strdup(configitem);
@@ -829,7 +831,13 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
 		pg_free(mine);
 		return;
 	}
-	*pos++ = '\0';
+	if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(mine, pos))
+	{
+		user_set = true;
+		*(pos - GUC_ARRAY_USERSET_SIGN_LEN) = '\0';
+	}
+	else
+		*pos++ = '\0';
 
 	/* Build the command, with suitable quoting for everything. */
 	appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
@@ -872,6 +880,10 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
 	else
 		appendStringLiteralConn(buf, pos, conn);
 
+	/* Add USER SET flag if specified in the string */
+	if (user_set)
+		appendPQExpBufferStr(buf, "USER SET;\n");
+
 	appendPQExpBufferStr(buf, ";\n");
 
 	pg_free(mine);
diff --git a/src/include/common/guc-common.h b/src/include/common/guc-common.h
new file mode 100644
index 00000000000..8b31953789e
--- /dev/null
+++ b/src/include/common/guc-common.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * guc-common.h
+ *	  Common declarations for Grand Unified Configuration.
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/guc-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef GUC_COMMON_H
+#define GUC_COMMON_H
+
+/*
+ * The designator of USER SET value in GUC array.
+ */
+#define GUC_ARRAY_USERSET_SIGN "(u)"
+#define GUC_ARRAY_USERSET_SIGN_LEN \
+	(sizeof(GUC_ARRAY_USERSET_SIGN) - 1)
+#define GUC_ARRAY_IS_USERSET_SIGN(s) \
+	(strncmp((s), GUC_ARRAY_USERSET_SIGN, GUC_ARRAY_USERSET_SIGN_LEN) == 0)
+#define GUC_ARRAY_IS_USERSET_SIGN_BEFORE(start, eqsign) \
+	((eqsign) - (start) >= GUC_ARRAY_USERSET_SIGN_LEN && \
+	 GUC_ARRAY_IS_USERSET_SIGN((eqsign) - GUC_ARRAY_USERSET_SIGN_LEN))
+#define GUC_ARRAY_IS_NAME_BORDER(s) \
+	((*(s)) == '=' || GUC_ARRAY_IS_USERSET_SIGN(s))
+
+#endif							/* GUC_COMMON_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7e7ad3f7e47..d48acda7c7b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2228,6 +2228,7 @@ typedef struct VariableSetStmt
 	char	   *name;			/* variable to be set */
 	List	   *args;			/* List of A_Const nodes */
 	bool		is_local;		/* SET LOCAL? */
+	bool		user_set;
 } VariableSetStmt;
 
 /* ----------------------
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index b3aaff9665b..9802973f086 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -12,6 +12,7 @@
 #ifndef GUC_H
 #define GUC_H
 
+#include "common/guc-common.h"
 #include "nodes/parsenodes.h"
 #include "tcop/dest.h"
 #include "utils/array.h"
@@ -393,7 +394,8 @@ extern char *GetConfigOptionByName(const char *name, const char **varname,
 
 extern void ProcessGUCArray(ArrayType *array,
 							GucContext context, GucSource source, GucAction action);
-extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value);
+extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name,
+							  const char *value, bool user_set);
 extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name);
 extern ArrayType *GUCArrayReset(ArrayType *array);
 
-- 
2.24.3 (Apple Git-128)

