2013-03-13 21:28 keltezéssel, Boszormenyi Zoltan írta:
2013-03-13 13:45 keltezéssel, Andres Freund írta:
On 2013-03-13 09:09:24 +0100, Boszormenyi Zoltan wrote:
2013-03-13 07:42 keltezéssel, Craig Ringer írta:
On 03/12/2013 06:27 AM, Craig Ringer wrote:
Think also about the case where someone wants to change multiple
values together and having just some set and not others would be
inconsistent.
Yeah, that's a killer. The reload would need to be scheduled for COMMIT
time, it can't be done by `SET PERSISTENT` directly.
Thinking about this some more, I'm not sure this is a good idea.

Right now, SET takes effect immediately. Always, without exception.
Delaying SET PERSISTENT's effects until commit would make it
inconsistent with SET's normal behaviour.

However, *not* delaying it would make it another quirky
not-transactional not-atomic command. That's OK, but if it's not going
to follow transactional semantics it should not be allowed to run within
a transaction, like VACUUM .

Writing the changes immediately but deferring the reload until commit
seems to be the worst of those two worlds.
I was thinking about it a little. There is a hook that runs at the end
of (sub-)transactions. It can be abused for this purpose to make
SET PERSISTENT transactional. The subtransactions can also stack
these settings, by forgetting settings upon ROLLBACK [ TO SAVEPOINT ]
and overwriting previous settings upon COMMIT and RELEASE SAVEPOINT.
All it needs is a list and maintaining intermediate pointers when entering
into a new level of SAVEPOINT. The functions to register such hooks are
in src/include/access/xact.h:

extern void RegisterXactCallback(XactCallback callback, void *arg);
extern void UnregisterXactCallback(XactCallback callback, void *arg);
extern void RegisterSubXactCallback(SubXactCallback callback, void *arg);
extern void UnregisterSubXactCallback(SubXactCallback callback, void *arg);
(sub)xact commit/abort already calls AtEOXact_GUC(commit, nestlevel). So you
wouldn't even need that.

Yes, thanks.

  It seems we could add another value to enum
GucStackState, like GUC_SET_PERSISTENT - and process those only if commit &&
nestlevel == 1.

Maybe it's not needed, only enum GucAction needs a new
GUC_ACTION_PERSISTENT value since that's what has any
business in push_old_value(). Adding two new members to
GucStack like these are enough
    bool has_persistent;
    config_var_value persistent;
and SET PERSISTENT can be treated as GUC_SET. push_old_value()
can merge GUC values set in the same transaction level.

It seems both were needed. The attached patch makes
SET PERSISTENT transactional and uses one setting per file.
It uses the currently existing parsing and validating code
and because of this, the patch is half the size of v12 from Amit.

Best regards,
Zoltán Böszörményi


Everytime you see one with commit && nestlevel > 1 you put them into them into
the stack one level up.

This seems like its somewhat in line with the way SET LOCAL is implemented?

On the other hand, it would make a lot of sense to implement it
as one setting per file or extending the  code to allow modifying
settings in bulk. The one setting per file should be easier and
it would also allow extensions to drop their settings automatically
into the automatic config directory. I don't know who mentioned
this idea about extensions but it also came up a while back.
I still think one-setting-per-file is the right way to go, yes.

Greetings,

Andres Freund





--
----------------------------------
Zoltán Böszörményi
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt, Austria
Web: http://www.postgresql-support.de
     http://www.postgresql.at/

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ae6ee60..8713fd8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -158,6 +158,22 @@ SET ENABLE_SEQSCAN TO OFF;
      require superuser permission to change via <command>SET</command> or
      <command>ALTER</>.
     </para>
+    
+    <para>
+     Another way to change configuration parameters persistently is by 
+     use of <xref linkend="SQL-SET">
+     command, for example:
+<screen>
+SET PERSISTENT max_connections To 10;
+</screen>
+     This command will allow users to change values persistently 
+     through SQL command. The new value will be effective immediately in
+     the session and the last persistent value for a transaction is
+     committed into its own configuration file. It becomes effective
+     cluster-wide after reloading the server configuration (<acronym>SIGHUP</>)
+     or server startup. The effect of this command is similar to when
+     user manually changes values in <filename>postgresql.conf</filename>.  
+    </para>
    </sect2>
 
    <sect2 id="config-setting-examining">
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 576fd65..ed19784 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -3388,6 +3388,13 @@
     <entry></entry>
    </row>
    <row>
+    <entry><token>PERSISTENT</token></entry>
+    <entry>non-reserved</entry>
+    <entry></entry>
+    <entry></entry>
+    <entry></entry>
+   </row>
+   <row>
     <entry><token>PLACING</token></entry>
     <entry>reserved</entry>
     <entry>non-reserved</entry>
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 21745db..5449362 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -21,8 +21,8 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-SET [ SESSION | LOCAL ] <replaceable class="PARAMETER">configuration_parameter</replaceable> { TO | = } { <replaceable class="PARAMETER">value</replaceable> | '<replaceable class="PARAMETER">value</replaceable>' | DEFAULT }
-SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</replaceable> | LOCAL | DEFAULT }
+SET [ SESSION | LOCAL | PERSISTENT ] <replaceable class="PARAMETER">configuration_parameter</replaceable> { TO | = } { <replaceable class="PARAMETER">value</replaceable> | '<replaceable class="PARAMETER">value</replaceable>' | DEFAULT }
+SET [ SESSION | LOCAL | PERSISTENT ] TIME ZONE { <replaceable class="PARAMETER">timezone</replaceable> | LOCAL | DEFAULT }
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
   </para>
 
   <para>
+   <command>SET PERSISTENT</command> writes the configuration parameter
+   value to the <filename><replaceable class="PARAMETER">configuration_parameter</replaceable>.conf</filename>
+   file. The new value will be effective immediately in the session and
+   is transactional. The last persistent value will be written to the
+   configuration file. The value written to the file will be effective
+   after reloading the server configuration (SIGHUP) or server startup.
+   With <literal>DEFAULT</literal>, it removes the <filename><replaceable class="PARAMETER">configuration_parameter</replaceable>.conf</filename>
+   file but the new effective value for the session will be what the
+   default was at the last server startup or configuration reloading.
+  </para>
+
+  <para>
    The effects of <command>SET LOCAL</command> last only till the end of
    the current transaction, whether committed or not.  A special case is
    <command>SET</command> followed by <command>SET LOCAL</command> within
@@ -98,8 +110,8 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
     <listitem>
      <para>
       Specifies that the command takes effect for the current session.
-      (This is the default if neither <literal>SESSION</> nor
-      <literal>LOCAL</> appears.)
+      (This is the default option, if any of <literal>SESSION</>,
+      <literal>LOCAL</> or <literal>PERSISTENT</> are not provided.)
      </para>
     </listitem>
    </varlistentry>
@@ -119,6 +131,19 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
    </varlistentry>
 
    <varlistentry>
+    <term><literal>PERSISTENT</></term>
+    <listitem>
+     <para>
+      Writes updated configuration parameters to
+      <filename><replaceable class="PARAMETER">configuration_parameter</replaceable>.conf</filename>
+      These configuration parameters take effect after reloading the
+      configuration file (SIGHUP) or in next server start based on
+      the type of configuration parameter modified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">configuration_parameter</replaceable></term>
     <listitem>
      <para>
@@ -154,7 +179,7 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
     <varlistentry>
      <term><literal>SCHEMA</literal></term>
      <listitem>
-      <para><literal>SET SCHEMA '<replaceable>value</>'</> is an alias for
+      <para><literal>SET [ PERSISTENT ] SCHEMA '<replaceable>value</>'</> is an alias for
        <literal>SET search_path TO <replaceable>value</></>.  Only one
        schema can be specified using this syntax.
       </para>
@@ -164,7 +189,7 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
     <varlistentry>
      <term><literal>NAMES</literal></term>
      <listitem>
-      <para><literal>SET NAMES <replaceable>value</></> is an alias for
+      <para><literal>SET [ PERSISTENT ] NAMES <replaceable>value</></> is an alias for
        <literal>SET client_encoding TO <replaceable>value</></>.
       </para>
      </listitem>
@@ -192,7 +217,7 @@ SELECT setseed(<replaceable>value</replaceable>);
     <varlistentry>
      <term><literal>TIME ZONE</literal></term>
      <listitem>
-      <para><literal>SET TIME ZONE <replaceable>value</></> is an alias
+      <para><literal>SET [ PERSISTENT ] TIME ZONE <replaceable>value</></> is an alias
        for <literal>SET timezone TO <replaceable>value</></>.  The
        syntax <literal>SET TIME ZONE</literal> allows special syntax
        for the time zone specification.  Here are examples of valid
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index e0a93c1..ceb5a5f 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -34,7 +34,8 @@ these required items, the cluster configuration files
 <filename>postgresql.conf</filename>, <filename>pg_hba.conf</filename>, and
 <filename>pg_ident.conf</filename> are traditionally stored in
 <varname>PGDATA</> (although in <productname>PostgreSQL</productname> 8.0 and
-later, it is possible to place them elsewhere).
+later, it is possible to place them elsewhere). By default the directory config is stored
+in <varname>PGDATA</>, however it should be kept along with <filename>postgresql.conf</filename>.
 </para>
 
 <table tocentry="1" id="pgdata-contents-table">
@@ -57,6 +58,11 @@ Item
 </row>
 
 <row>
+ <entry><filename>config</></entry>
+ <entry>Subdirectory containing automatically generated configuration files</entry>
+</row>
+
+<row>
  <entry><filename>base</></entry>
  <entry>Subdirectory containing per-database subdirectories</entry>
 </row>
@@ -253,6 +259,11 @@ where <replaceable>PPP</> is the PID of the owning backend and
 <replaceable>NNN</> distinguishes different temporary files of that backend.
 </para>
 
+<para>
+Configuration variables changed by command <acronym>SET PERSISTENT</> will be stored in 
+<varname>PGDATA</><filename>/config</>.
+</para>
+
 </sect1>
 
 <sect1 id="storage-toast">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 6b7cb5b..80d8e5b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -818,11 +818,13 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 	if (client_min_messages < WARNING)
 		(void) set_config_option("client_min_messages", "warning",
 								 PGC_USERSET, PGC_S_SESSION,
-								 GUC_ACTION_SAVE, true, 0);
+								 GUC_ACTION_SAVE, GUC_ACTION_NONE,
+								 true, 0);
 	if (log_min_messages < WARNING)
 		(void) set_config_option("log_min_messages", "warning",
 								 PGC_SUSET, PGC_S_SESSION,
-								 GUC_ACTION_SAVE, true, 0);
+								 GUC_ACTION_SAVE, GUC_ACTION_NONE,
+								 true, 0);
 
 	/*
 	 * Set up the search path to contain the target schema, then the schemas
@@ -847,7 +849,8 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
 
 	(void) set_config_option("search_path", pathbuf.data,
 							 PGC_USERSET, PGC_S_SESSION,
-							 GUC_ACTION_SAVE, true, 0);
+							 GUC_ACTION_SAVE, GUC_ACTION_NONE,
+							 true, 0);
 
 	/*
 	 * Set creating_extension and related variables so that
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index fd3823a..ffcb037 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3281,6 +3281,7 @@ _copyVariableSetStmt(const VariableSetStmt *from)
 	COPY_STRING_FIELD(name);
 	COPY_NODE_FIELD(args);
 	COPY_SCALAR_FIELD(is_local);
+	COPY_SCALAR_FIELD(is_persistent);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 085cd5b..d3dab8f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1568,6 +1568,7 @@ _equalVariableSetStmt(const VariableSetStmt *a, const VariableSetStmt *b)
 	COMPARE_STRING_FIELD(name);
 	COMPARE_NODE_FIELD(args);
 	COMPARE_SCALAR_FIELD(is_local);
+	COMPARE_SCALAR_FIELD(is_persistent);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9d07f30..8cb388c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -404,7 +404,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <istmt>	insert_rest
 
-%type <vsetstmt> set_rest set_rest_more SetResetClause FunctionSetResetClause
+%type <vsetstmt> set_rest set_rest_more set_persistent SetResetClause FunctionSetResetClause
 
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
@@ -573,7 +573,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
+	PARSER PARTIAL PARTITION PASSING PASSWORD PERSISTENT PLACING PLANS POSITION
 	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -1312,6 +1312,12 @@ VariableSetStmt:
 					n->is_local = false;
 					$$ = (Node *) n;
 				}
+			| SET PERSISTENT set_persistent
+				{
+					VariableSetStmt *n = $3;
+					n->is_persistent = true;
+					$$ = (Node *) n;
+				}
 		;
 
 set_rest:
@@ -1335,6 +1341,51 @@ set_rest:
 			;
 
 set_rest_more:	/* Generic SET syntaxes: */
+			CATALOG_P Sconst
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("current database cannot be changed"),
+							 parser_errposition(@2)));
+					$$ = NULL; /*not reached*/
+				}
+			| ROLE ColId_or_Sconst
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->kind = VAR_SET_VALUE;
+					n->name = "role";
+					n->args = list_make1(makeStringConst($2, @2));
+					$$ = n;
+				}
+			| SESSION AUTHORIZATION ColId_or_Sconst
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->kind = VAR_SET_VALUE;
+					n->name = "session_authorization";
+					n->args = list_make1(makeStringConst($3, @3));
+					$$ = n;
+				}
+			| SESSION AUTHORIZATION DEFAULT
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->kind = VAR_SET_DEFAULT;
+					n->name = "session_authorization";
+					$$ = n;
+				}
+			/* Special syntaxes invented by PostgreSQL: */
+			| TRANSACTION SNAPSHOT Sconst
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->kind = VAR_SET_MULTI;
+					n->name = "TRANSACTION SNAPSHOT";
+					n->args = list_make1(makeStringConst($3, @3));
+					$$ = n;
+				}
+			| set_persistent
+			;
+
+/* common SET/SET PERSISTENT syntaxes */
+set_persistent:
 			var_name TO var_list
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1373,6 +1424,14 @@ set_rest_more:	/* Generic SET syntaxes: */
 					$$ = n;
 				}
 			/* Special syntaxes mandated by SQL standard: */
+			| SCHEMA Sconst
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->kind = VAR_SET_VALUE;
+					n->name = "search_path";
+					n->args = list_make1(makeStringConst($2, @2));
+					$$ = n;
+				}
 			| TIME ZONE zone_value
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1384,22 +1443,6 @@ set_rest_more:	/* Generic SET syntaxes: */
 						n->kind = VAR_SET_DEFAULT;
 					$$ = n;
 				}
-			| CATALOG_P Sconst
-				{
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("current database cannot be changed"),
-							 parser_errposition(@2)));
-					$$ = NULL; /*not reached*/
-				}
-			| SCHEMA Sconst
-				{
-					VariableSetStmt *n = makeNode(VariableSetStmt);
-					n->kind = VAR_SET_VALUE;
-					n->name = "search_path";
-					n->args = list_make1(makeStringConst($2, @2));
-					$$ = n;
-				}
 			| NAMES opt_encoding
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1411,29 +1454,6 @@ set_rest_more:	/* Generic SET syntaxes: */
 						n->kind = VAR_SET_DEFAULT;
 					$$ = n;
 				}
-			| ROLE ColId_or_Sconst
-				{
-					VariableSetStmt *n = makeNode(VariableSetStmt);
-					n->kind = VAR_SET_VALUE;
-					n->name = "role";
-					n->args = list_make1(makeStringConst($2, @2));
-					$$ = n;
-				}
-			| SESSION AUTHORIZATION ColId_or_Sconst
-				{
-					VariableSetStmt *n = makeNode(VariableSetStmt);
-					n->kind = VAR_SET_VALUE;
-					n->name = "session_authorization";
-					n->args = list_make1(makeStringConst($3, @3));
-					$$ = n;
-				}
-			| SESSION AUTHORIZATION DEFAULT
-				{
-					VariableSetStmt *n = makeNode(VariableSetStmt);
-					n->kind = VAR_SET_DEFAULT;
-					n->name = "session_authorization";
-					$$ = n;
-				}
 			| XML_P OPTION document_or_content
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1442,16 +1462,7 @@ set_rest_more:	/* Generic SET syntaxes: */
 					n->args = list_make1(makeStringConst($3 == XMLOPTION_DOCUMENT ? "DOCUMENT" : "CONTENT", @3));
 					$$ = n;
 				}
-			/* Special syntaxes invented by PostgreSQL: */
-			| TRANSACTION SNAPSHOT Sconst
-				{
-					VariableSetStmt *n = makeNode(VariableSetStmt);
-					n->kind = VAR_SET_MULTI;
-					n->name = "TRANSACTION SNAPSHOT";
-					n->args = list_make1(makeStringConst($3, @3));
-					$$ = n;
-				}
-		;
+			;
 
 var_name:	ColId								{ $$ = $1; }
 			| var_name '.' ColId
@@ -12826,6 +12837,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERSISTENT
 			| PLANS
 			| PRECEDING
 			| PREPARE
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 4322844..2cbeb2d 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2408,7 +2408,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem);
 	(void) set_config_option("work_mem", workmembuf,
 							 PGC_USERSET, PGC_S_SESSION,
-							 GUC_ACTION_SAVE, true, 0);
+							 GUC_ACTION_SAVE, GUC_ACTION_NONE,
+							 true, 0);
 
 	if (SPI_connect() != SPI_OK_CONNECT)
 		elog(ERROR, "SPI_connect failed");
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index d3565a5..b9c0aab 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -246,7 +246,7 @@ ProcessConfigFile(GucContext context)
 		/* Now we can re-apply the wired-in default (i.e., the boot_val) */
 		if (set_config_option(gconf->name, NULL,
 							  context, PGC_S_DEFAULT,
-							  GUC_ACTION_SET, true, 0) > 0)
+							  GUC_ACTION_SET, GUC_ACTION_NONE, true, 0) > 0)
 		{
 			/* Log the change if appropriate */
 			if (context == PGC_SIGHUP)
@@ -301,7 +301,7 @@ ProcessConfigFile(GucContext context)
 
 		scres = set_config_option(item->name, item->value,
 								  context, PGC_S_FILE,
-								  GUC_ACTION_SET, true, 0);
+								  GUC_ACTION_SET, GUC_ACTION_NONE, true, 0);
 		if (scres > 0)
 		{
 			/* variable was updated, so log the change if appropriate */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 98149fc..73794cd 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -138,6 +138,8 @@ extern bool optimize_bounded_sort;
 
 static int	GUC_check_errcode_value;
 
+static char	   *GUC_autodir = NULL;
+
 /* global variables for check hook support */
 char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
@@ -4335,6 +4337,10 @@ push_old_value(struct config_generic * gconf, GucAction action)
 		Assert(stack->nest_level == GUCNestLevel);
 		switch (action)
 		{
+			case GUC_ACTION_NONE:
+				/* push_old_value must not be called with this action */
+				Assert(action != GUC_ACTION_NONE);
+				break;
 			case GUC_ACTION_SET:
 				/* SET overrides any prior action at same nest level */
 				if (stack->state == GUC_SET_LOCAL)
@@ -4358,6 +4364,17 @@ push_old_value(struct config_generic * gconf, GucAction action)
 				/* Could only have a prior SAVE of same variable */
 				Assert(stack->state == GUC_SAVE);
 				break;
+			case GUC_ACTION_PERSISTENT:
+				stack->remove_persistent = false;
+				stack->has_persistent = true;
+				set_stack_value(gconf, &stack->persistent);
+				break;
+			case GUC_ACTION_REMOVE_PERSISTENT:
+				if (stack->has_persistent)
+					discard_stack_value(gconf, &stack->persistent);
+				stack->remove_persistent = true;
+				stack->has_persistent = false;
+				break;
 		}
 		Assert(guc_dirty);		/* must be set already */
 		return;
@@ -4375,19 +4392,38 @@ push_old_value(struct config_generic * gconf, GucAction action)
 	stack->nest_level = GUCNestLevel;
 	switch (action)
 	{
+		case GUC_ACTION_NONE:
+			/* push_old_value must not be called with this action */
+			Assert(action != GUC_ACTION_NONE);
+			break;
 		case GUC_ACTION_SET:
 			stack->state = GUC_SET;
 			break;
 		case GUC_ACTION_LOCAL:
 			stack->state = GUC_LOCAL;
 			break;
+		case GUC_ACTION_PERSISTENT:
+		case GUC_ACTION_REMOVE_PERSISTENT:
+			stack->state = GUC_PERSISTENT;
+			break;
 		case GUC_ACTION_SAVE:
 			stack->state = GUC_SAVE;
 			break;
 	}
 	stack->source = gconf->source;
 	stack->scontext = gconf->scontext;
-	set_stack_value(gconf, &stack->prior);
+	if (stack->state == GUC_PERSISTENT)
+	{
+		if (action == GUC_ACTION_PERSISTENT)
+		{
+			set_stack_value(gconf, &stack->persistent);
+			stack->has_persistent = true;
+		}
+		else
+			stack->remove_persistent = true;
+	}
+	else
+		set_stack_value(gconf, &stack->prior);
 
 	gconf->stack = stack;
 
@@ -4425,6 +4461,108 @@ NewGUCNestLevel(void)
 	return ++GUCNestLevel;
 }
 
+static void
+SetPersistentDir(void)
+{
+	if (GUC_autodir == NULL)
+	{
+		char	config_dir[MAXPGPATH];
+
+		strncpy(config_dir, ConfigFileName, sizeof(config_dir));
+		get_parent_directory(config_dir);
+		strcat(config_dir, "/");
+		strcat(config_dir, PG_AUTOCONF_DIR);
+		GUC_autodir = (char *) MemoryContextAllocZero(TopMemoryContext, MAXPGPATH);
+		strcpy(GUC_autodir, config_dir);
+	}
+}
+
+/*
+ * Write a persistent GUC into its own file and write only warnings
+ * when the file cannot be written. This is because we are COMMITting
+ * the transaction, we cannot rollback by throwing an elog(ERROR).
+ * Returns false if an error happened, to allow warning once.
+ */
+static bool
+WritePersistentGUC(struct config_generic *gconf, config_var_value *value, bool warn)
+{
+	char		tmp_file[MAXPGPATH];
+	char		config_file[MAXPGPATH];
+	int		fd, retval;
+	StringInfoData	buf;
+	char	   *escaped;
+
+	SetPersistentDir();
+
+	sprintf(tmp_file, "%s/%s.conf.%d", GUC_autodir, gconf->name, (int) getpid());
+
+	fd = open(tmp_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
+	if (fd == -1)
+	{
+		if (warn)
+			ereport(WARNING,
+					(errcode_for_file_access(),
+					 errmsg("Failed to open temporary file: %m")));
+		return false;
+	}
+
+	initStringInfo(&buf);
+	switch (gconf->vartype)
+	{
+		case PGC_BOOL:
+			appendStringInfo(&buf, "%s = %s\n", gconf->name, (value->val.boolval ? "on" : "off"));
+			break;
+		case PGC_INT:
+			appendStringInfo(&buf, "%s = %d\n", gconf->name, value->val.intval);
+			break;
+		case PGC_REAL:
+			appendStringInfo(&buf, "%s = %lf\n", gconf->name, value->val.realval);
+			break;
+		case PGC_STRING:
+			escaped = escape_single_quotes_ascii(value->val.stringval);
+			appendStringInfo(&buf, "%s = '%s'\n", gconf->name, escaped);
+			free(escaped);
+			break;
+		case PGC_ENUM:
+			appendStringInfo(&buf, "%s = '%s'\n", gconf->name, 
+						config_enum_lookup_by_value((struct config_enum *)gconf,
+												value->val.enumval));
+			break;
+	}
+
+	retval = write(fd, buf.data, buf.len);
+
+	pfree(buf.data);
+
+	if (retval != buf.len)
+	{
+		if (warn)
+			ereport(WARNING,
+					(errcode_for_file_access(),
+					 errmsg("Failed to open temporary file: %m")));
+		close(fd);
+		unlink(tmp_file);
+		return false;
+	}
+
+	close(fd);
+
+	sprintf(config_file, "%s/%s.conf", GUC_autodir, gconf->name);
+	rename(tmp_file, config_file);
+
+	return true;
+}
+
+static void
+RemovePersistentGUC(struct config_generic *gconf)
+{
+	char		config_file[MAXPGPATH];
+
+	SetPersistentDir();
+	sprintf(config_file, "%s/%s.conf", GUC_autodir, gconf->name);
+	unlink(config_file);
+}
+
 /*
  * Do GUC processing at transaction or subtransaction commit or abort, or
  * when exiting a function that has proconfig settings, or when undoing a
@@ -4437,6 +4575,7 @@ void
 AtEOXact_GUC(bool isCommit, int nestLevel)
 {
 	bool		still_dirty;
+	bool		warn_persistent = true;
 	int			i;
 
 	/*
@@ -4491,10 +4630,18 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 				/* transaction commit */
 				if (stack->state == GUC_SET_LOCAL)
 					restoreMasked = true;
-				else if (stack->state == GUC_SET)
+				else if (stack->state == GUC_SET || stack->state == GUC_PERSISTENT)
 				{
 					/* we keep the current active value */
 					discard_stack_value(gconf, &stack->prior);
+					if (stack->has_persistent)
+					{
+						bool	retval = WritePersistentGUC(gconf, &stack->persistent,
+												warn_persistent);
+						warn_persistent = warn_persistent && retval;
+					}
+					if (stack->remove_persistent)
+						RemovePersistentGUC(gconf);
 				}
 				else	/* must be GUC_LOCAL */
 					restorePrior = true;
@@ -4522,6 +4669,14 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 						discard_stack_value(gconf, &stack->prior);
 						if (prev->state == GUC_SET_LOCAL)
 							discard_stack_value(gconf, &prev->masked);
+						if (stack->has_persistent || stack->remove_persistent)
+						{
+							if (prev->has_persistent)
+								discard_stack_value(gconf, &prev->persistent);
+							prev->remove_persistent = stack->remove_persistent;
+							prev->has_persistent = stack->has_persistent;
+							prev->persistent = stack->persistent;
+						}
 						prev->state = GUC_SET;
 						break;
 
@@ -4548,8 +4703,22 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 						if (prev->state == GUC_SET_LOCAL)
 							discard_stack_value(gconf, &prev->masked);
 						prev->masked = stack->masked;
+						if (stack->has_persistent || stack->remove_persistent)
+						{
+							if (prev->has_persistent)
+								discard_stack_value(gconf, &prev->persistent);
+							prev->remove_persistent = stack->remove_persistent;
+							prev->has_persistent = stack->has_persistent;
+							prev->persistent = stack->persistent;
+						}
 						prev->state = GUC_SET_LOCAL;
 						break;
+
+					case GUC_PERSISTENT:
+						prev->remove_persistent = stack->remove_persistent;
+						prev->has_persistent = stack->has_persistent;
+						prev->persistent = stack->persistent;
+						break;
 				}
 			}
 
@@ -5134,7 +5303,8 @@ config_enum_get_options(struct config_enum * record, const char *prefix,
 int
 set_config_option(const char *name, const char *value,
 				  GucContext context, GucSource source,
-				  GucAction action, bool changeVal, int elevel)
+				  GucAction action, GucAction action2,
+				  bool changeVal, int elevel)
 {
 	struct config_generic *record;
 	bool		prohibitValueChange = false;
@@ -5396,6 +5566,9 @@ set_config_option(const char *name, const char *value,
 									newextra);
 					conf->gen.source = source;
 					conf->gen.scontext = context;
+
+					if (!makeDefault && (action2 == GUC_ACTION_PERSISTENT))
+						push_old_value(&conf->gen, GUC_ACTION_PERSISTENT);
 				}
 				if (makeDefault)
 				{
@@ -5421,6 +5594,8 @@ set_config_option(const char *name, const char *value,
 						}
 					}
 				}
+				if (action2 == GUC_ACTION_REMOVE_PERSISTENT)
+					push_old_value(&conf->gen, GUC_ACTION_REMOVE_PERSISTENT);
 
 				/* Perhaps we didn't install newextra anywhere */
 				if (newextra && !extra_field_used(&conf->gen, newextra))
@@ -5500,6 +5675,9 @@ set_config_option(const char *name, const char *value,
 									newextra);
 					conf->gen.source = source;
 					conf->gen.scontext = context;
+
+					if (!makeDefault && action2 == GUC_ACTION_PERSISTENT)
+						push_old_value(&conf->gen, GUC_ACTION_PERSISTENT);
 				}
 				if (makeDefault)
 				{
@@ -5525,6 +5703,8 @@ set_config_option(const char *name, const char *value,
 						}
 					}
 				}
+				if (action2 == GUC_ACTION_REMOVE_PERSISTENT)
+					push_old_value(&conf->gen, GUC_ACTION_REMOVE_PERSISTENT);
 
 				/* Perhaps we didn't install newextra anywhere */
 				if (newextra && !extra_field_used(&conf->gen, newextra))
@@ -5601,6 +5781,9 @@ set_config_option(const char *name, const char *value,
 									newextra);
 					conf->gen.source = source;
 					conf->gen.scontext = context;
+
+					if (!makeDefault && action2 == GUC_ACTION_PERSISTENT)
+						push_old_value(&conf->gen, GUC_ACTION_PERSISTENT);
 				}
 				if (makeDefault)
 				{
@@ -5626,6 +5809,8 @@ set_config_option(const char *name, const char *value,
 						}
 					}
 				}
+				if (action2 == GUC_ACTION_REMOVE_PERSISTENT)
+					push_old_value(&conf->gen, GUC_ACTION_REMOVE_PERSISTENT);
 
 				/* Perhaps we didn't install newextra anywhere */
 				if (newextra && !extra_field_used(&conf->gen, newextra))
@@ -5722,6 +5907,9 @@ set_config_option(const char *name, const char *value,
 									newextra);
 					conf->gen.source = source;
 					conf->gen.scontext = context;
+
+					if (!makeDefault && action2 == GUC_ACTION_PERSISTENT)
+						push_old_value(&conf->gen, GUC_ACTION_PERSISTENT);
 				}
 
 				if (makeDefault)
@@ -5749,6 +5937,8 @@ set_config_option(const char *name, const char *value,
 						}
 					}
 				}
+				if (action2 == GUC_ACTION_REMOVE_PERSISTENT)
+					push_old_value(&conf->gen, GUC_ACTION_REMOVE_PERSISTENT);
 
 				/* Perhaps we didn't install newval anywhere */
 				if (newval && !string_field_used(conf, newval))
@@ -5830,6 +6020,9 @@ set_config_option(const char *name, const char *value,
 									newextra);
 					conf->gen.source = source;
 					conf->gen.scontext = context;
+
+					if (!makeDefault && action2 == GUC_ACTION_PERSISTENT)
+						push_old_value(&conf->gen, GUC_ACTION_PERSISTENT);
 				}
 				if (makeDefault)
 				{
@@ -5855,6 +6048,8 @@ set_config_option(const char *name, const char *value,
 						}
 					}
 				}
+				if (action2 == GUC_ACTION_REMOVE_PERSISTENT)
+					push_old_value(&conf->gen, GUC_ACTION_REMOVE_PERSISTENT);
 
 				/* Perhaps we didn't install newextra anywhere */
 				if (newextra && !extra_field_used(&conf->gen, newextra))
@@ -5912,7 +6107,7 @@ SetConfigOption(const char *name, const char *value,
 				GucContext context, GucSource source)
 {
 	(void) set_config_option(name, value, context, source,
-							 GUC_ACTION_SET, true, 0);
+						 GUC_ACTION_SET, GUC_ACTION_NONE, true, 0);
 }
 
 
@@ -6174,6 +6369,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
 									 (superuser() ? PGC_SUSET : PGC_USERSET),
 									 PGC_S_SESSION,
 									 action,
+									 (stmt->is_persistent ? GUC_ACTION_PERSISTENT : GUC_ACTION_NONE),
 									 true,
 									 0);
 			break;
@@ -6253,6 +6449,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
 									 (superuser() ? PGC_SUSET : PGC_USERSET),
 									 PGC_S_SESSION,
 									 action,
+									 (stmt->is_persistent ? GUC_ACTION_REMOVE_PERSISTENT : GUC_ACTION_NONE),
 									 true,
 									 0);
 			break;
@@ -6299,6 +6496,7 @@ SetPGVariable(const char *name, List *args, bool is_local)
 							 (superuser() ? PGC_SUSET : PGC_USERSET),
 							 PGC_S_SESSION,
 							 is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
+							 GUC_ACTION_NONE,
 							 true,
 							 0);
 }
@@ -6343,6 +6541,7 @@ set_config_by_name(PG_FUNCTION_ARGS)
 							 (superuser() ? PGC_SUSET : PGC_USERSET),
 							 PGC_S_SESSION,
 							 is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
+							 GUC_ACTION_NONE,
 							 true,
 							 0);
 
@@ -6465,7 +6664,8 @@ define_custom_variable(struct config_generic * variable)
 		(void) set_config_option(name, pHolder->reset_val,
 								 pHolder->gen.reset_scontext,
 								 pHolder->gen.reset_source,
-								 GUC_ACTION_SET, true, WARNING);
+								 GUC_ACTION_SET, GUC_ACTION_NONE,
+								 true, WARNING);
 	/* That should not have resulted in stacking anything */
 	Assert(variable->stack == NULL);
 
@@ -6521,30 +6721,36 @@ reapply_stacked_values(struct config_generic * variable,
 			case GUC_SAVE:
 				(void) set_config_option(name, curvalue,
 										 curscontext, cursource,
-										 GUC_ACTION_SAVE, true, WARNING);
+										 GUC_ACTION_SAVE, GUC_ACTION_NONE,
+										 true, WARNING);
 				break;
 
 			case GUC_SET:
+			case GUC_PERSISTENT:
 				(void) set_config_option(name, curvalue,
 										 curscontext, cursource,
-										 GUC_ACTION_SET, true, WARNING);
+										 GUC_ACTION_SET, GUC_ACTION_NONE,
+										 true, WARNING);
 				break;
 
 			case GUC_LOCAL:
 				(void) set_config_option(name, curvalue,
 										 curscontext, cursource,
-										 GUC_ACTION_LOCAL, true, WARNING);
+										 GUC_ACTION_LOCAL, GUC_ACTION_NONE,
+										 true, WARNING);
 				break;
 
 			case GUC_SET_LOCAL:
 				/* first, apply the masked value as SET */
 				(void) set_config_option(name, stack->masked.val.stringval,
 									   stack->masked_scontext, PGC_S_SESSION,
-										 GUC_ACTION_SET, true, WARNING);
+										 GUC_ACTION_SET, GUC_ACTION_NONE,
+										 true, WARNING);
 				/* then apply the current value as LOCAL */
 				(void) set_config_option(name, curvalue,
 										 curscontext, cursource,
-										 GUC_ACTION_LOCAL, true, WARNING);
+										 GUC_ACTION_LOCAL, GUC_ACTION_NONE,
+										 true, WARNING);
 				break;
 		}
 
@@ -6568,7 +6774,8 @@ reapply_stacked_values(struct config_generic * variable,
 		{
 			(void) set_config_option(name, curvalue,
 									 curscontext, cursource,
-									 GUC_ACTION_SET, true, WARNING);
+									 GUC_ACTION_SET, GUC_ACTION_NONE,
+									 true, WARNING);
 			variable->stack = NULL;
 		}
 	}
@@ -7681,7 +7888,8 @@ read_nondefault_variables(void)
 
 		(void) set_config_option(varname, varvalue,
 								 varscontext, varsource,
-								 GUC_ACTION_SET, true, 0);
+								 GUC_ACTION_SET, GUC_ACTION_NONE,
+								 true, 0);
 		if (varsourcefile[0])
 			set_config_sourcefile(varname, varsourcefile, varsourceline);
 
@@ -7784,7 +7992,8 @@ ProcessGUCArray(ArrayType *array,
 
 		(void) set_config_option(name, value,
 								 context, source,
-								 action, true, 0);
+								 action, GUC_ACTION_NONE,
+								 true, 0);
 
 		free(name);
 		if (value)
@@ -8088,7 +8297,7 @@ validate_option_array_item(const char *name, const char *value,
 	/* test for permissions and valid option value */
 	(void) set_config_option(name, value,
 							 superuser() ? PGC_SUSET : PGC_USERSET,
-							 PGC_S_TEST, GUC_ACTION_SET, false, 0);
+							 PGC_S_TEST, GUC_ACTION_SET, GUC_ACTION_NONE, false, 0);
 
 	return true;
 }
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 62aea2f..144639b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -581,6 +581,15 @@
 #include_if_exists = 'exists.conf'	# include file only if it exists
 #include = 'special.conf'		# include file
 
+# This includes the default configuration directory included to support
+# SET PERSISTENT statement.
+#
+# WARNING: User should not remove below include_dir or directory config, 
+#          as both are required to make SET PERSISTENT command work.
+#          Any configuration parameter values specified after this line 
+#          will override the values set by SET PERSISTENT statement.
+#include_dir = 'config'
+
 #------------------------------------------------------------------------------
 # CUSTOMIZED OPTIONS
 #------------------------------------------------------------------------------
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index b0b346f..2e190ba 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -193,7 +193,8 @@ const char *subdirs[] = {
 	"base/1",
 	"pg_tblspc",
 	"pg_stat",
-	"pg_stat_tmp"
+	"pg_stat_tmp",
+	PG_AUTOCONF_DIR
 };
 
 
@@ -1262,6 +1263,9 @@ setup_config(void)
 		conflines = replace_token(conflines, "#log_timezone = 'GMT'", repltok);
 	}
 
+	snprintf(repltok, sizeof(repltok), "include_dir = '" PG_AUTOCONF_DIR "'");
+	conflines = replace_token(conflines, "#include_dir = 'config'", repltok);
+
 	snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data);
 
 	writefile(path, conflines);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2229ef0..2861706 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1438,6 +1438,7 @@ typedef struct VariableSetStmt
 	char	   *name;			/* variable to be set */
 	List	   *args;			/* List of A_Const nodes */
 	bool		is_local;		/* SET LOCAL? */
+	bool		is_persistent;	/* SET PERSISTENT? */
 } VariableSetStmt;
 
 /* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 68a13b7..bd9d338 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -280,6 +280,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("persistent", PERSISTENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 02bcd92..92083f1 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -266,3 +266,8 @@
 /* #define HEAPDEBUGALL */
 /* #define ACLDEBUG */
 /* #define RTDEBUG */
+
+/*
+ * Automatic configuration directory for SET PERSISTENT.
+ */
+#define PG_AUTOCONF_DIR			"config"
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index d497b1f..e35dc36 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -159,9 +159,12 @@ typedef const char *(*GucShowHook) (void);
 typedef enum
 {
 	/* Types of set_config_option actions */
+	GUC_ACTION_NONE,			/* dummy action */
 	GUC_ACTION_SET,				/* regular SET command */
 	GUC_ACTION_LOCAL,			/* SET LOCAL command */
-	GUC_ACTION_SAVE				/* function SET option, or temp assignment */
+	GUC_ACTION_SAVE,			/* function SET option, or temp assignment */
+	GUC_ACTION_PERSISTENT,			/* SET PERSISTENT command */
+	GUC_ACTION_REMOVE_PERSISTENT		/* SET PERSISTENT TO DEFAULT command */
 } GucAction;
 
 #define GUC_QUALIFIER_SEPARATOR '.'
@@ -321,7 +324,8 @@ extern bool parse_int(const char *value, int *result, int flags,
 extern bool parse_real(const char *value, double *result);
 extern int set_config_option(const char *name, const char *value,
 				  GucContext context, GucSource source,
-				  GucAction action, bool changeVal, int elevel);
+				  GucAction action, GucAction action2,
+				  bool changeVal, int elevel);
 extern char *GetConfigOptionByName(const char *name, const char **varname);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
 extern int	GetNumConfigOptions(void);
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 7360ce3..6e7985a 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -109,7 +109,8 @@ typedef enum
 	GUC_SAVE,					/* entry caused by function SET option */
 	GUC_SET,					/* entry caused by plain SET command */
 	GUC_LOCAL,					/* entry caused by SET LOCAL command */
-	GUC_SET_LOCAL				/* entry caused by SET then SET LOCAL */
+	GUC_SET_LOCAL,				/* entry caused by SET then SET LOCAL */
+	GUC_PERSISTENT					/* entry caused by SET PERSISTENT */
 } GucStackState;
 
 typedef struct guc_stack
@@ -123,6 +124,9 @@ typedef struct guc_stack
 	GucContext	masked_scontext;	/* context that set the masked value */
 	config_var_value prior;		/* previous value of variable */
 	config_var_value masked;	/* SET value in a GUC_SET_LOCAL entry */
+	bool		remove_persistent;
+	bool		has_persistent;	/* the value below is valid */
+	config_var_value persistent;	/* SET value in a GUC_SET_PERSISTENT entry */
 } GucStack;
 
 /*
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to