set_persistent_one_setting_per_file.patch
text/x-patch
Filename: set_persistent_one_setting_per_file.patch
Type: text/x-patch
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| doc/src/sgml/config.sgml | 16 | 0 |
| doc/src/sgml/keywords.sgml | 7 | 0 |
| doc/src/sgml/ref/set.sgml | 32 | 7 |
| doc/src/sgml/storage.sgml | 12 | 1 |
| src/backend/commands/extension.c | 6 | 3 |
| src/backend/nodes/copyfuncs.c | 1 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/parser/gram.y | 63 | 51 |
| src/backend/utils/adt/ri_triggers.c | 2 | 1 |
| src/backend/utils/misc/guc.c | 223 | 14 |
| src/backend/utils/misc/guc-file.l | 2 | 2 |
| src/backend/utils/misc/postgresql.conf.sample | 9 | 0 |
| src/bin/initdb/initdb.c | 5 | 1 |
| src/include/nodes/parsenodes.h | 1 | 0 |
| src/include/parser/kwlist.h | 1 | 0 |
| src/include/pg_config_manual.h | 5 | 0 |
| src/include/utils/guc.h | 6 | 2 |
| src/include/utils/guc_tables.h | 5 | 1 |
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;
/*