summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTatsuo Ishii <ishii@postgresql.org>2013-12-18 23:42:44 +0900
committerTatsuo Ishii <ishii@postgresql.org>2013-12-18 23:42:44 +0900
commit65d6e4cb5c62371dae6c236a7e709d503ae6ddf8 (patch)
tree07fff22fb42940bcf618885589909de0adaa9f9c
parentdba5a9dda9adbda16a72c46e1c012ee6552c248a (diff)
downloadpostgresql-65d6e4cb5c62371dae6c236a7e709d503ae6ddf8.tar.gz
Add ALTER SYSTEM command to edit the server configuration file.
Patch contributed by Amit Kapila. Reviewed by Hari Babu, Masao Fujii, Boszormenyi Zoltan, Andres Freund, Greg Smith and others.
-rw-r--r--doc/src/sgml/config.sgml13
-rw-r--r--doc/src/sgml/ref/allfiles.sgml1
-rw-r--r--doc/src/sgml/ref/alter_system.sgml114
-rw-r--r--doc/src/sgml/reference.sgml1
-rw-r--r--doc/src/sgml/storage.sgml6
-rw-r--r--src/backend/nodes/copyfuncs.c13
-rw-r--r--src/backend/nodes/equalfuncs.c12
-rw-r--r--src/backend/parser/gram.y27
-rw-r--r--src/backend/replication/basebackup.c7
-rw-r--r--src/backend/tcop/utility.c13
-rw-r--r--src/backend/utils/misc/guc-file.l35
-rw-r--r--src/backend/utils/misc/guc.c614
-rw-r--r--src/bin/initdb/initdb.c16
-rw-r--r--src/include/nodes/nodes.h1
-rw-r--r--src/include/nodes/parsenodes.h10
-rw-r--r--src/include/pg_config_manual.h7
-rw-r--r--src/include/storage/lwlock.h1
-rw-r--r--src/include/utils/guc.h1
18 files changed, 797 insertions, 95 deletions
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f0794467ba..5575df5160 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -158,6 +158,19 @@ 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-ALTERSYSTEM">
+ command, for example:
+<screen>
+ALTER SYSTEM SET checkpoint_timeout TO 600;
+</screen>
+ This command will allow users to change values persistently
+ through SQL command. The values will be effective after reload of 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/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 5846974feb..ce7a5e3cb6 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -30,6 +30,7 @@ Complete list of usable sgml source files in this directory.
<!ENTITY alterSchema SYSTEM "alter_schema.sgml">
<!ENTITY alterServer SYSTEM "alter_server.sgml">
<!ENTITY alterSequence SYSTEM "alter_sequence.sgml">
+<!ENTITY alterSystem SYSTEM "alter_system.sgml">
<!ENTITY alterTable SYSTEM "alter_table.sgml">
<!ENTITY alterTableSpace SYSTEM "alter_tablespace.sgml">
<!ENTITY alterTSConfig SYSTEM "alter_tsconfig.sgml">
diff --git a/doc/src/sgml/ref/alter_system.sgml b/doc/src/sgml/ref/alter_system.sgml
new file mode 100644
index 0000000000..3ccc6afd51
--- /dev/null
+++ b/doc/src/sgml/ref/alter_system.sgml
@@ -0,0 +1,114 @@
+<!--
+doc/src/sgml/ref/alter_system.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERSYSTEM">
+ <refmeta>
+ <refentrytitle>ALTER SYSTEM</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ALTER SYSTEM</refname>
+ <refpurpose>change a server configuration parameter</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-altersystem">
+ <primary>ALTER SYSTEM</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER SYSTEM SET <replaceable class="PARAMETER">configuration_parameter</replaceable> { TO | = } { <replaceable class="PARAMETER">value</replaceable> | '<replaceable class="PARAMETER">value</replaceable>' | DEFAULT }
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>ALTER SYSTEM</command> writes the configuration parameter
+ values to the <filename>postgresql.auto.conf</filename> file. With
+ <literal>DEFAULT</literal>, it removes a configuration entry from
+ <filename>postgresql.auto.conf</filename> file. The values will be
+ effective after reload of server configuration (SIGHUP) or in next
+ server start based on the type of configuration parameter modified.
+ </para>
+
+ <para>
+ This command is not allowed inside transaction block or function.
+ </para>
+
+ <para>
+ See <xref linkend="config-setting"> for other ways to set the parameters and
+ how they become effective.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">configuration_parameter</replaceable></term>
+ <listitem>
+ <para>
+ Name of a settable run-time parameter. Available parameters are
+ documented in <xref linkend="runtime-config">.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">value</replaceable></term>
+ <listitem>
+ <para>
+ New value of parameter. Values can be specified as string
+ constants, identifiers, numbers, or comma-separated lists of
+ these, as appropriate for the particular parameter.
+ <literal>DEFAULT</literal> can be written to specify to remove the
+ parameter and its value from <filename>postgresql.auto.conf</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>
+ Set the <literal>wal_level</>:
+<programlisting>
+ALTER SYSTEM SET wal_level = hot_standby;
+</programlisting>
+ </para>
+
+ <para>
+ Set the <literal>authentication_timeout</>:
+<programlisting>
+ALTER SYSTEM SET authentication_timeout = 10;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ The <command>ALTER SYSTEM</command> statement is a
+ <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="SQL-SET"></member>
+ <member><xref linkend="SQL-SHOW"></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index d967f666b9..87e8e9ee8f 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -58,6 +58,7 @@
&alterSchema;
&alterSequence;
&alterServer;
+ &alterSystem;
&alterTable;
&alterTableSpace;
&alterTSConfig;
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 09b3f1028c..1f3f1f9bf9 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -126,6 +126,12 @@ Item
</row>
<row>
+ <entry><filename>postgresql.auto.conf</></entry>
+ <entry>A file used for storing configuration parameters that are set by
+<command>ALTER SYSTEM</command></entry>
+</row>
+
+<row>
<entry><filename>postmaster.opts</></entry>
<entry>A file recording the command-line options the server was
last started with</entry>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index cd8a11b8d5..3e102310c5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3292,6 +3292,16 @@ _copyReplicaIdentityStmt(const ReplicaIdentityStmt *from)
return newnode;
}
+static AlterSystemStmt *
+_copyAlterSystemStmt(const AlterSystemStmt * from)
+{
+ AlterSystemStmt *newnode = makeNode(AlterSystemStmt);
+
+ COPY_NODE_FIELD(setstmt);
+
+ return newnode;
+}
+
static CreateSeqStmt *
_copyCreateSeqStmt(const CreateSeqStmt *from)
{
@@ -4368,6 +4378,9 @@ copyObject(const void *from)
case T_ReplicaIdentityStmt:
retval = _copyReplicaIdentityStmt(from);
break;
+ case T_AlterSystemStmt:
+ retval = _copyAlterSystemStmt(from);
+ break;
case T_CreateSeqStmt:
retval = _copyCreateSeqStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6188114060..329755c703 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1547,6 +1547,15 @@ _equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStm
}
static bool
+_equalAlterSystemStmt(const AlterSystemStmt * a, const AlterSystemStmt * b)
+{
+ COMPARE_NODE_FIELD(setstmt);
+
+ return true;
+}
+
+
+static bool
_equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
{
COMPARE_NODE_FIELD(sequence);
@@ -2838,6 +2847,9 @@ equal(const void *a, const void *b)
case T_ReplicaIdentityStmt:
retval = _equalReplicaIdentityStmt(a, b);
break;
+ case T_AlterSystemStmt:
+ retval = _equalAlterSystemStmt(a, b);
+ break;
case T_CreateSeqStmt:
retval = _equalCreateSeqStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f9d45777ca..b4e5552636 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -216,7 +216,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
- AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
+ AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
AlterRoleStmt AlterRoleSetStmt
@@ -397,7 +397,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> generic_set set_rest set_rest_more SetResetClause FunctionSetResetClause
%type <node> TableElement TypedTableElement ConstraintElem TableFuncElement
%type <node> columnDef columnOptions
@@ -724,6 +724,7 @@ stmt :
| AlterObjectSchemaStmt
| AlterOwnerStmt
| AlterSeqStmt
+ | AlterSystemStmt
| AlterTableStmt
| AlterCompositeTypeStmt
| AlterRoleSetStmt
@@ -1333,7 +1334,7 @@ set_rest:
| set_rest_more
;
-set_rest_more: /* Generic SET syntaxes: */
+generic_set:
var_name TO var_list
{
VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -1364,6 +1365,9 @@ set_rest_more: /* Generic SET syntaxes: */
n->name = $1;
$$ = n;
}
+
+set_rest_more: /* Generic SET syntaxes: */
+ generic_set {$$ = $1;}
| var_name FROM CURRENT_P
{
VariableSetStmt *n = makeNode(VariableSetStmt);
@@ -8312,6 +8316,23 @@ DropdbStmt: DROP DATABASE database_name
/*****************************************************************************
*
+ * ALTER SYSTEM SET
+ *
+ * This is used to change configuration parameters persistently.
+ *****************************************************************************/
+
+AlterSystemStmt:
+ ALTER SYSTEM_P SET generic_set
+ {
+ AlterSystemStmt *n = makeNode(AlterSystemStmt);
+ n->setstmt = $4;
+ $$ = (Node *)n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
* Manipulate a domain
*
*****************************************************************************/
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index ba8d173357..244e3b0ab3 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -811,6 +811,13 @@ sendDir(char *path, int basepathlen, bool sizeonly)
strlen(PG_TEMP_FILE_PREFIX)) == 0)
continue;
+ /* skip auto conf temporary file */
+ if (strncmp(de->d_name,
+ PG_AUTOCONF_FILENAME ".temp",
+ sizeof(PG_AUTOCONF_FILENAME) + 4) == 0)
+ continue;
+
+
/*
* If there's a backup_label file, it belongs to a backup started by
* the user with pg_start_backup(). It is *not* correct for this
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 7d75b3383f..dca4503471 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -687,6 +687,11 @@ standard_ProcessUtility(Node *parsetree,
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
+ case T_AlterSystemStmt:
+ PreventTransactionChain(isTopLevel, "ALTER SYSTEM");
+ AlterSystemSetConfigFile((AlterSystemStmt *) parsetree);
+ break;
+
case T_VariableSetStmt:
ExecSetVariableStmt((VariableSetStmt *) parsetree, isTopLevel);
break;
@@ -2157,6 +2162,10 @@ CreateCommandTag(Node *parsetree)
tag = "REFRESH MATERIALIZED VIEW";
break;
+ case T_AlterSystemStmt:
+ tag = "ALTER SYSTEM";
+ break;
+
case T_VariableSetStmt:
switch (((VariableSetStmt *) parsetree)->kind)
{
@@ -2726,6 +2735,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_AlterSystemStmt:
+ lev = LOGSTMT_ALL;
+ break;
+
case T_VariableSetStmt:
lev = LOGSTMT_ALL;
break;
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index c5ca4a4074..640899bae5 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -120,6 +120,9 @@ ProcessConfigFile(GucContext context)
*head,
*tail;
int i;
+ char ConfigAutoFileName[MAXPGPATH];
+ char *ErrorConfFile;
+ char *CallingFileName;
/*
* Config files are processed on startup (by the postmaster only)
@@ -134,6 +137,8 @@ ProcessConfigFile(GucContext context)
*/
elevel = IsUnderPostmaster ? DEBUG2 : LOG;
+ ErrorConfFile = ConfigFileName;
+
/* Parse the file into a list of option names and values */
head = tail = NULL;
@@ -145,6 +150,26 @@ ProcessConfigFile(GucContext context)
}
/*
+ * Parse postgresql.auto.conf file after postgresql.conf to replace
+ * parameters set by ALTER SYSTEM command. This file is present in
+ * data directory, however when called during initdb data directory is not
+ * set till this point, so use ConfigFile path which will be same.
+ */
+ snprintf(ConfigAutoFileName,sizeof(ConfigAutoFileName),"%s", PG_AUTOCONF_FILENAME);
+ if (data_directory)
+ CallingFileName = NULL;
+ else
+ CallingFileName = ConfigFileName;
+
+ if (!ParseConfigFile(ConfigAutoFileName, CallingFileName, false, 0, elevel, &head, &tail))
+ {
+ /* Syntax error(s) detected in the file, so bail out */
+ error = true;
+ ErrorConfFile = ConfigAutoFileName;
+ goto cleanup_list;
+ }
+
+ /*
* Mark all extant GUC variables as not present in the config file.
* We need this so that we can tell below which ones have been removed
* from the file since we last processed it.
@@ -192,6 +217,7 @@ ProcessConfigFile(GucContext context)
item->name,
item->filename, item->sourceline)));
error = true;
+ ErrorConfFile = item->filename;
}
}
@@ -318,7 +344,10 @@ ProcessConfigFile(GucContext context)
}
}
else if (scres == 0)
+ {
error = true;
+ ErrorConfFile = item->filename;
+ }
/* else no error but variable's active value was not changed */
/*
@@ -348,17 +377,17 @@ ProcessConfigFile(GucContext context)
ereport(ERROR,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors",
- ConfigFileName)));
+ ErrorConfFile)));
else if (apply)
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors; unaffected changes were applied",
- ConfigFileName)));
+ ErrorConfFile)));
else
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("configuration file \"%s\" contains errors; no changes were applied",
- ConfigFileName)));
+ ErrorConfFile)));
}
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b0c14a2dfc..51416f49cd 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -207,6 +207,10 @@ static char *config_enum_get_options(struct config_enum * record,
const char *prefix, const char *suffix,
const char *separator);
+static bool validate_conf_option(struct config_generic * record,
+ const char *name, const char *value, GucSource source,
+ int elevel, bool freemem, void *newval, void **newextra);
+
/*
* Options for enum values defined in this module.
@@ -3484,6 +3488,9 @@ static void ShowAllGUCConfig(DestReceiver *dest);
static char *_ShowOption(struct config_generic * record, bool use_units);
static bool validate_option_array_item(const char *name, const char *value,
bool skipIfNoPermissions);
+static void write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p);
+static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
+ char *config_file, char *name, char *value);
/*
@@ -5248,6 +5255,220 @@ config_enum_get_options(struct config_enum * record, const char *prefix,
return retstr.data;
}
+/*
+ * Validates configuration parameter and value, by calling check hook functions
+ * depending on record's vartype. It validates if the parameter
+ * value given is in range of expected predefined value for that parameter.
+ *
+ * freemem - true indicates memory for newval and newextra will be
+ * freed in this function, false indicates it will be freed
+ * by caller.
+ * Return value:
+ * 1: the value is valid
+ * 0: the name or value is invalid
+ */
+bool
+validate_conf_option(struct config_generic * record, const char *name,
+ const char *value, GucSource source, int elevel,
+ bool freemem, void *newval, void **newextra)
+{
+ /*
+ * Validate the value for the passed record, to ensure it is in expected
+ * range.
+ */
+ switch (record->vartype)
+ {
+
+ case PGC_BOOL:
+ {
+ struct config_bool *conf = (struct config_bool *) record;
+ bool tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ if (!parse_bool(value, newval))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a Boolean value",
+ name)));
+ return 0;
+ }
+
+ if (!call_bool_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_INT:
+ {
+ struct config_int *conf = (struct config_int *) record;
+ int tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ const char *hintmsg;
+
+ if (!parse_int(value, newval, conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return 0;
+ }
+
+ if (*((int *) newval) < conf->min || *((int *) newval) > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)",
+ *((int *) newval), name, conf->min, conf->max)));
+ return 0;
+ }
+
+ if (!call_int_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_REAL:
+ {
+ struct config_real *conf = (struct config_real *) record;
+ double tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ if (!parse_real(value, newval))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" requires a numeric value",
+ name)));
+ return 0;
+ }
+
+ if (*((double *) newval) < conf->min || *((double *) newval) > conf->max)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)",
+ *((double *) newval), name, conf->min, conf->max)));
+ return 0;
+ }
+
+ if (!call_real_check_hook(conf, newval, newextra,
+ source, elevel))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ case PGC_STRING:
+ {
+ struct config_string *conf = (struct config_string *) record;
+ char *tempPtr;
+ char **tmpnewval = newval;
+
+ if (newval == NULL)
+ tmpnewval = &tempPtr;
+
+ if (value != NULL)
+ {
+ /*
+ * The value passed by the caller could be transient, so
+ * we always strdup it.
+ */
+ *tmpnewval = guc_strdup(elevel, value);
+ if (*tmpnewval == NULL)
+ return 0;
+
+ /*
+ * The only built-in "parsing" check we have is to apply
+ * truncation if GUC_IS_NAME.
+ */
+ if (conf->gen.flags & GUC_IS_NAME)
+ truncate_identifier(*tmpnewval, strlen(*tmpnewval), true);
+
+ if (!call_string_check_hook(conf, tmpnewval, newextra,
+ source, elevel))
+ {
+ free(*tmpnewval);
+ return 0;
+ }
+
+ /* Free the malloc'd data if any */
+ if (freemem)
+ {
+ if (*tmpnewval != NULL)
+ free(*tmpnewval);
+ if (*newextra != NULL)
+ free(*newextra);
+ }
+ }
+ }
+ break;
+ case PGC_ENUM:
+ {
+ struct config_enum *conf = (struct config_enum *) record;
+ int tmpnewval;
+
+ if (newval == NULL)
+ newval = &tmpnewval;
+
+ if (value != NULL)
+ {
+ if (!config_enum_lookup_by_name(conf, value, newval))
+ {
+ char *hintmsg;
+
+ hintmsg = config_enum_get_options(conf,
+ "Available values: ",
+ ".", ", ");
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+
+ if (hintmsg != NULL)
+ pfree(hintmsg);
+ return 0;
+ }
+ if (!call_enum_check_hook(conf, newval, newextra,
+ source, LOG))
+ return 0;
+
+ if (*newextra && freemem)
+ free(*newextra);
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
/*
* Sets option `name' to given value.
@@ -5496,16 +5717,9 @@ set_config_option(const char *name, const char *value,
if (value)
{
- if (!parse_bool(value, &newval))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- name)));
- return 0;
- }
- if (!call_bool_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -5589,27 +5803,9 @@ set_config_option(const char *name, const char *value,
if (value)
{
- const char *hintmsg;
-
- if (!parse_int(value, &newval, conf->gen.flags, &hintmsg))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for parameter \"%s\": \"%s\"",
- name, value),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
- return 0;
- }
- if (newval < conf->min || newval > conf->max)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("%d is outside the valid range for parameter \"%s\" (%d .. %d)",
- newval, name, conf->min, conf->max)));
- return 0;
- }
- if (!call_int_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -5693,24 +5889,9 @@ set_config_option(const char *name, const char *value,
if (value)
{
- if (!parse_real(value, &newval))
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a numeric value",
- name)));
- return 0;
- }
- if (newval < conf->min || newval > conf->max)
- {
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("%g is outside the valid range for parameter \"%s\" (%g .. %g)",
- newval, name, conf->min, conf->max)));
- return 0;
- }
- if (!call_real_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -5794,27 +5975,10 @@ set_config_option(const char *name, const char *value,
if (value)
{
- /*
- * The value passed by the caller could be transient, so
- * we always strdup it.
- */
- newval = guc_strdup(elevel, value);
- if (newval == NULL)
- return 0;
-
- /*
- * The only built-in "parsing" check we have is to apply
- * truncation if GUC_IS_NAME.
- */
- if (conf->gen.flags & GUC_IS_NAME)
- truncate_identifier(newval, strlen(newval), true);
-
- if (!call_string_check_hook(conf, &newval, &newextra,
- source, elevel))
- {
- free(newval);
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
- }
}
else if (source == PGC_S_DEFAULT)
{
@@ -5920,26 +6084,9 @@ set_config_option(const char *name, const char *value,
if (value)
{
- if (!config_enum_lookup_by_name(conf, value, &newval))
- {
- char *hintmsg;
-
- hintmsg = config_enum_get_options(conf,
- "Available values: ",
- ".", ", ");
-
- ereport(elevel,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for parameter \"%s\": \"%s\"",
- name, value),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
-
- if (hintmsg)
- pfree(hintmsg);
- return 0;
- }
- if (!call_enum_check_hook(conf, &newval, &newextra,
- source, elevel))
+ if (!validate_conf_option(record, name, value, source,
+ elevel, false, &newval,
+ &newextra))
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -6309,6 +6456,295 @@ flatten_set_variable_args(const char *name, List *args)
return buf.data;
}
+/*
+ * Writes updated configuration parameter values into
+ * postgresql.auto.conf.temp file. It traverses the list of parameters
+ * and quote the string values before writing them to temporaray file.
+ */
+static void
+write_auto_conf_file(int fd, const char *filename, ConfigVariable **head_p)
+{
+ ConfigVariable *item;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ appendStringInfoString(&buf, "# Do not edit this file manually! \n");
+ appendStringInfoString(&buf, "# It will be overwritten by ALTER SYSTEM command. \n");
+
+ /*
+ * write the file header message before contents, so that if there is no
+ * item it can contain message
+ */
+ if (write(fd, buf.data, buf.len) < 0)
+ ereport(ERROR,
+ (errmsg("failed to write to \"%s\" file", filename)));
+ resetStringInfo(&buf);
+
+ /*
+ * traverse the list of parameters, quote the string parameter and write
+ * it to file. Once all parameters are written fsync the file.
+ */
+
+ for (item = *head_p; item != NULL; item = item->next)
+ {
+ char *escaped;
+
+ appendStringInfoString(&buf, item->name);
+ appendStringInfoString(&buf, " = ");
+
+ appendStringInfoString(&buf, "\'");
+ escaped = escape_single_quotes_ascii(item->value);
+ appendStringInfoString(&buf, escaped);
+ free(escaped);
+ appendStringInfoString(&buf, "\'");
+
+ appendStringInfoString(&buf, "\n");
+
+ if (write(fd, buf.data, buf.len) < 0)
+ ereport(ERROR,
+ (errmsg("failed to write to \"%s\" file", filename)));
+ resetStringInfo(&buf);
+ }
+
+ if (pg_fsync(fd) != 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not fsync file \"%s\": %m", filename)));
+
+ pfree(buf.data);
+}
+
+
+/*
+ * This function takes list of all configuration parameters in
+ * postgresql.auto.conf and parameter to be updated as input arguments and
+ * replace the updated configuration parameter value in a list. If the
+ * parameter to be updated is new then it is appended to the list of
+ * parameters.
+ */
+static void
+replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
+ char *config_file,
+ char *name, char *value)
+{
+ ConfigVariable *item,
+ *prev = NULL;
+
+ if (*head_p != NULL)
+ {
+ for (item = *head_p; item != NULL; item = item->next)
+ {
+ if (strcmp(item->name, name) == 0)
+ {
+ pfree(item->value);
+ if (value != NULL)
+ /* update the parameter value */
+ item->value = pstrdup(value);
+ else
+ {
+ /* delete the configuration parameter from list */
+ if (*head_p == item)
+ *head_p = item->next;
+ else
+ prev->next = item->next;
+
+ if (*tail_p == item)
+ *tail_p = prev;
+
+ pfree(item->name);
+ pfree(item->filename);
+ pfree(item);
+ }
+ return;
+ }
+ prev = item;
+ }
+ }
+
+ if (value == NULL)
+ return;
+
+ item = palloc(sizeof *item);
+ item->name = pstrdup(name);
+ item->value = pstrdup(value);
+ item->filename = pstrdup(config_file);
+ item->next = NULL;
+
+ if (*head_p == NULL)
+ {
+ item->sourceline = 1;
+ *head_p = item;
+ }
+ else
+ {
+ item->sourceline = (*tail_p)->sourceline + 1;
+ (*tail_p)->next = item;
+ }
+
+ *tail_p = item;
+
+ return;
+}
+
+
+/*
+ * Persist the configuration parameter value.
+ *
+ * This function takes all previous configuration parameters
+ * set by ALTER SYSTEM command and the currently set ones
+ * and write them all to the automatic configuration file.
+ *
+ * The configuration parameters are written to a temporary
+ * file then renamed to the final name. The template for the
+ * temporary file is postgresql.auto.conf.temp.
+ *
+ * An LWLock is used to serialize writing to the same file.
+ *
+ * In case of an error, we leave the original automatic
+ * configuration file (postgresql.auto.conf) intact.
+ */
+void
+AlterSystemSetConfigFile(AlterSystemStmt * altersysstmt)
+{
+ char *name;
+ char *value;
+ int Tmpfd = -1;
+ FILE *infile;
+ struct config_generic *record;
+ ConfigVariable *head = NULL;
+ ConfigVariable *tail = NULL;
+ char AutoConfFileName[MAXPGPATH];
+ char AutoConfTmpFileName[MAXPGPATH];
+ struct stat st;
+ void *newextra = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to execute ALTER SYSTEM command"))));
+
+ /*
+ * Validate the name and arguments [value1, value2 ... ].
+ */
+ name = altersysstmt->setstmt->name;
+
+ switch (altersysstmt->setstmt->kind)
+ {
+ case VAR_SET_VALUE:
+ value = ExtractSetVariableArgs(altersysstmt->setstmt);
+ break;
+
+ case VAR_SET_DEFAULT:
+ value = NULL;
+ break;
+ default:
+ elog(ERROR, "unrecognized alter system stmt type: %d",
+ altersysstmt->setstmt->kind);
+ break;
+ }
+
+ record = find_option(name, false, LOG);
+ if (record == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("unrecognized configuration parameter \"%s\"", name)));
+
+ if ((record->context == PGC_INTERNAL) ||
+ (record->flags & GUC_DISALLOW_IN_FILE))
+ ereport(ERROR,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed",
+ name)));
+
+ if (!validate_conf_option(record, name, value, PGC_S_FILE,
+ ERROR, true, NULL,
+ &newextra))
+ ereport(ERROR,
+ (errmsg("invalid value for parameter \"%s\": \"%s\"", name, value)));
+
+
+ /*
+ * Use data directory as reference path for postgresql.auto.conf and it's
+ * corresponding temp file
+ */
+ join_path_components(AutoConfFileName, data_directory, PG_AUTOCONF_FILENAME);
+ canonicalize_path(AutoConfFileName);
+ snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s",
+ AutoConfFileName,
+ "temp");
+
+ /*
+ * one backend is allowed to operate on postgresql.auto.conf file, to
+ * ensure that we need to update the contents of the file with
+ * AutoFileLock. To ensure crash safety, first the contents are written to
+ * temporary file and then rename it to postgresql.auto.conf. In case
+ * there exists a temp file from previous crash, that can be reused.
+ */
+
+ LWLockAcquire(AutoFileLock, LW_EXCLUSIVE);
+
+ Tmpfd = open(AutoConfTmpFileName, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (Tmpfd < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("failed to open auto conf temp file \"%s\": %m ",
+ AutoConfTmpFileName)));
+
+ PG_TRY();
+ {
+ if (stat(AutoConfFileName, &st) == 0)
+ {
+ /* open postgresql.auto.conf file */
+ infile = AllocateFile(AutoConfFileName, "r");
+ if (infile == NULL)
+ ereport(ERROR,
+ (errmsg("failed to open auto conf file \"%s\": %m ",
+ AutoConfFileName)));
+
+ /* Parse the postgresql.auto.conf file */
+ ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail);
+
+ FreeFile(infile);
+ }
+
+ /*
+ * replace with new value if the configuration parameter already
+ * exists OR add it as a new cofiguration parameter in the file.
+ */
+ replace_auto_config_value(&head, &tail, AutoConfFileName, name, value);
+
+ /* Write and sync the New contents to postgresql.auto.conf.temp file */
+ write_auto_conf_file(Tmpfd, AutoConfTmpFileName, &head);
+
+ close(Tmpfd);
+ Tmpfd = -1;
+
+ /*
+ * As the rename is atomic operation, if any problem occurs after this
+ * at max it can loose the parameters set by last ALTER SYSTEM
+ * command.
+ */
+ if (rename(AutoConfTmpFileName, AutoConfFileName) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not rename file \"%s\" to \"%s\" : %m",
+ AutoConfTmpFileName, AutoConfFileName)));
+ }
+ PG_CATCH();
+ {
+ if (Tmpfd >= 0)
+ close(Tmpfd);
+
+ unlink(AutoConfTmpFileName);
+ FreeConfigVariables(head);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ FreeConfigVariables(head);
+ LWLockRelease(AutoFileLock);
+ return;
+}
/*
* SET command
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 30e3701f92..e6bb132bea 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1228,6 +1228,7 @@ setup_config(void)
char repltok[MAXPGPATH];
char path[MAXPGPATH];
const char *default_timezone;
+ char *autoconflines[3];
fputs(_("creating configuration files ... "), stdout);
fflush(stdout);
@@ -1320,6 +1321,21 @@ setup_config(void)
writefile(path, conflines);
chmod(path, S_IRUSR | S_IWUSR);
+ /*
+ * create the automatic configuration file to store the configuration
+ * parameters set by ALTER SYSTEM command. The parameters present in this
+ * file will override the value of parameters that exists before parse of
+ * this file.
+ */
+ autoconflines[0] = pg_strdup("# Do not edit this file manually! \n");
+ autoconflines[1] = pg_strdup("# It will be overwritten by the ALTER SYSTEM command. \n");
+ autoconflines[2] = NULL;
+
+ sprintf(path, "%s/%s", pg_data, PG_AUTOCONF_FILENAME);
+
+ writefile(path, autoconflines);
+ chmod(path, S_IRUSR | S_IWUSR);
+
free(conflines);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ff9af7691c..a68c8ad5ca 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -363,6 +363,7 @@ typedef enum NodeTag
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
+ T_AlterSystemStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0ad7586853..6a5a8c5f2d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2472,6 +2472,16 @@ typedef struct DropdbStmt
} DropdbStmt;
/* ----------------------
+ * Alter System Statement
+ * ----------------------
+ */
+typedef struct AlterSystemStmt
+{
+ NodeTag type;
+ VariableSetStmt *setstmt; /* SET subcommand */
+} AlterSystemStmt;
+
+/* ----------------------
* Cluster Statement (support pbrown's cluster index implementation)
* ----------------------
*/
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 2e6aad1ca5..9d1166305d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -292,3 +292,10 @@
/* #define HEAPDEBUGALL */
/* #define ACLDEBUG */
/* #define RTDEBUG */
+
+/*
+ * Automatic configuration file name for ALTER SYSTEM.
+ * This file will be used to store values of configuration parameters
+ * set by ALTER SYSTEM command
+ */
+#define PG_AUTOCONF_FILENAME "postgresql.auto.conf"
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 730c47ba68..3e42f6a468 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -81,6 +81,7 @@ typedef enum LWLockId
SyncRepLock,
BackgroundWorkerLock,
DynamicSharedMemoryControlLock,
+ AutoFileLock,
/* Individual lock IDs end here */
FirstBufMappingLock,
FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 3e981b3e94..0a02999e3f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -326,6 +326,7 @@ 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);
+extern void AlterSystemSetConfigFile(AlterSystemStmt * setstmt);
extern char *GetConfigOptionByName(const char *name, const char **varname);
extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
extern int GetNumConfigOptions(void);