diff options
author | Tatsuo Ishii <ishii@postgresql.org> | 2013-12-18 23:42:44 +0900 |
---|---|---|
committer | Tatsuo Ishii <ishii@postgresql.org> | 2013-12-18 23:42:44 +0900 |
commit | 65d6e4cb5c62371dae6c236a7e709d503ae6ddf8 (patch) | |
tree | 07fff22fb42940bcf618885589909de0adaa9f9c | |
parent | dba5a9dda9adbda16a72c46e1c012ee6552c248a (diff) | |
download | postgresql-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.sgml | 13 | ||||
-rw-r--r-- | doc/src/sgml/ref/allfiles.sgml | 1 | ||||
-rw-r--r-- | doc/src/sgml/ref/alter_system.sgml | 114 | ||||
-rw-r--r-- | doc/src/sgml/reference.sgml | 1 | ||||
-rw-r--r-- | doc/src/sgml/storage.sgml | 6 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 13 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 12 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 27 | ||||
-rw-r--r-- | src/backend/replication/basebackup.c | 7 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 13 | ||||
-rw-r--r-- | src/backend/utils/misc/guc-file.l | 35 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 614 | ||||
-rw-r--r-- | src/bin/initdb/initdb.c | 16 | ||||
-rw-r--r-- | src/include/nodes/nodes.h | 1 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 10 | ||||
-rw-r--r-- | src/include/pg_config_manual.h | 7 | ||||
-rw-r--r-- | src/include/storage/lwlock.h | 1 | ||||
-rw-r--r-- | src/include/utils/guc.h | 1 |
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); |