]> granicus.if.org Git - postgresql/commitdiff
Add ALTER SYSTEM command to edit the server configuration file.
authorTatsuo Ishii <ishii@postgresql.org>
Wed, 18 Dec 2013 14:42:44 +0000 (23:42 +0900)
committerTatsuo Ishii <ishii@postgresql.org>
Wed, 18 Dec 2013 14:42:44 +0000 (23:42 +0900)
Patch contributed by Amit Kapila. Reviewed by Hari Babu, Masao Fujii,
Boszormenyi Zoltan, Andres Freund, Greg Smith and others.

18 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/ref/allfiles.sgml
doc/src/sgml/ref/alter_system.sgml [new file with mode: 0644]
doc/src/sgml/reference.sgml
doc/src/sgml/storage.sgml
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/replication/basebackup.c
src/backend/tcop/utility.c
src/backend/utils/misc/guc-file.l
src/backend/utils/misc/guc.c
src/bin/initdb/initdb.c
src/include/nodes/nodes.h
src/include/nodes/parsenodes.h
src/include/pg_config_manual.h
src/include/storage/lwlock.h
src/include/utils/guc.h

index f0794467ba47c748717cfb99b65061818d9038ff..5575df516027769d72cd0973946eeca443358353 100644 (file)
@@ -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">
index 5846974feb9b5c9e49f37ba9b444e55d743a181c..ce7a5e3cb6cc250ea7c816254afb0101bea3882a 100644 (file)
@@ -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 (file)
index 0000000..3ccc6af
--- /dev/null
@@ -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>
index d967f666b9166ebbdbb1eb976b6672f907f4c634..87e8e9ee8ffdf0a4ac3406479a290ecfc4b374cf 100644 (file)
@@ -58,6 +58,7 @@
    &alterSchema;
    &alterSequence;
    &alterServer;
+   &alterSystem;
    &alterTable;
    &alterTableSpace;
    &alterTSConfig;
index 09b3f1028c120a7433bd5917886bfa113726191c..1f3f1f9bf97983b02c5f0c055f6f8c4145a818f6 100644 (file)
@@ -125,6 +125,12 @@ Item
  <entry>Subdirectory containing WAL (Write Ahead Log) files</entry>
 </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
index cd8a11b8d5d7702b6bc7d318ab95080830666c4b..3e102310c5915d7e17a44d7684984ac59bc4b84d 100644 (file)
@@ -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;
index 6188114060f83a8afca777e07a2595a94641b889..329755c703f0d72af3a4fae888deeb3693d0bac9 100644 (file)
@@ -1546,6 +1546,15 @@ _equalReplicaIdentityStmt(const ReplicaIdentityStmt *a, const ReplicaIdentityStm
        return true;
 }
 
+static bool
+_equalAlterSystemStmt(const AlterSystemStmt * a, const AlterSystemStmt * b)
+{
+       COMPARE_NODE_FIELD(setstmt);
+
+       return true;
+}
+
+
 static bool
 _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
 {
@@ -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;
index f9d45777ca7dfcfff2e2b892dcc31d723cb57e8f..b4e5552636e811a9ad33067b37a5c9a20d0d2e89 100644 (file)
@@ -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);
@@ -8310,6 +8314,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
index ba8d173357e036e9d9242a2a4a9d640c59780318..244e3b0ab3f8465a8e851352d667adc710a8b4cc 100644 (file)
@@ -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
index 7d75b3383fa9f2f7a3e3d2521ad610da57c95b7a..dca4503471eb26755232e6c8184521851a0d301c 100644 (file)
@@ -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;
index c5ca4a40742b3a8fdd4bb555731a1032af3de130..640899bae580027496d771046e7b76174ee6e298 100644 (file)
@@ -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;
 
@@ -144,6 +149,26 @@ ProcessConfigFile(GucContext context)
                goto cleanup_list;
        }
 
+       /*
+        * 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
@@ -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)));
        }
 }
 
index b0c14a2dfce41b1631e15ce3b26c6b5feaeb49e9..51416f49cd9b2e3b6fdbd6ac9a464ae30bc93470 100644 (file)
@@ -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
index 30e3701f92a6cf03a8993546e9d7f51bdd214fb7..e6bb132beaf69d38967f0fc9c94319d03a4ae755 100644 (file)
@@ -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);
 
 
index ff9af7691c91af86275a7a651a4201c5fed377ec..a68c8ad5cac9f2d5ae9af0f59e3254ef0cdf435a 100644 (file)
@@ -363,6 +363,7 @@ typedef enum NodeTag
        T_AlterEventTrigStmt,
        T_RefreshMatViewStmt,
        T_ReplicaIdentityStmt,
+       T_AlterSystemStmt,
 
        /*
         * TAGS FOR PARSE TREE NODES (parsenodes.h)
index 0ad7586853b3b1b62be3d26a5fa112d1ea28c292..6a5a8c5f2d74b4dcd0981ab3113317a58dbdc71b 100644 (file)
@@ -2471,6 +2471,16 @@ typedef struct DropdbStmt
        bool            missing_ok;             /* skip error if db is missing? */
 } DropdbStmt;
 
+/* ----------------------
+ *             Alter System Statement
+ * ----------------------
+ */
+typedef struct AlterSystemStmt
+{
+       NodeTag         type;
+       VariableSetStmt *setstmt;       /* SET subcommand */
+}      AlterSystemStmt;
+
 /* ----------------------
  *             Cluster Statement (support pbrown's cluster index implementation)
  * ----------------------
index 2e6aad1ca56aa8cceedf6622ba91a9aa8475c688..9d1166305d3d0e780538da5680e3288addef27f9 100644 (file)
 /* #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"
index 730c47ba68691f9162da094ba0cad13ebb08bd2e..3e42f6a468fb7b3afca70892ba08528f07e38036 100644 (file)
@@ -81,6 +81,7 @@ typedef enum LWLockId
        SyncRepLock,
        BackgroundWorkerLock,
        DynamicSharedMemoryControlLock,
+       AutoFileLock,
        /* Individual lock IDs end here */
        FirstBufMappingLock,
        FirstLockMgrLock = FirstBufMappingLock + NUM_BUFFER_PARTITIONS,
index 3e981b3e94b4ce2f6eb3ecc1323ec006f4c85ee7..0a02999e3f83a19df8b83067941abe4a0657a742 100644 (file)
@@ -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);