ALTER SESSION - Mailing list pgsql-hackers

From Kyotaro HORIGUCHI
Subject ALTER SESSION
Date
Msg-id 20190129.203254.115361483.horiguchi.kyotaro@lab.ntt.co.jp
Whole thread Raw
Responses Re: ALTER SESSION  (Kyotaro HORIGUCHI <horiguchi.kyotaro@lab.ntt.co.jp>)
Re: ALTER SESSION  (Andres Freund <andres@anarazel.de>)
List pgsql-hackers
Hello.

https://www.postgresql.org/message-id/20190128.133143.115303042.horiguchi.kyotaro@lab.ntt.co.jp

> C. Provide session-specific GUC variable (that overides the global one)
>    - Add new configuration file "postgresql.conf.<PID>" and
>      pg_reload_conf() let the session with the PID loads it as if
>      it is the last included file. All such files are removed at
>      startup or at the end of the coressponding session.
> 
>    - Add a new syntax like this:
>      ALTER SESSION WITH (pid=xxxx)
>         SET configuration_parameter {TO | =} {value | 'value' | DEFAULT}
>         RESET configuration_parameter
>         RESET ALL
> 
>    - Target variables are marked with GUC_REMOTE.
> 
> I'll consider the last choice and will come up with a patch.

This is that, with a small change in design.

ALTER SESSION WITH (pid <pid>) SET param {TO|=} value [ IMMEDIATE ]
ALTER SESSION WITH (pid <pid>) RESET param [ IMMEDIATE ]
ALTER SESSION WITH (pid <pid>) RESET ALL

The first form create an entry in
$PGDATA/pg_session_conf/postgresql.<beid>.conf.
The second form removes the entry.
The third form removes the file itself.

IMMEDIATE specifies that the change is applied immediately by
sending SIGHUP to the process. pg_reload_conf() works as well.

The session configuration is removed at session-end and the
directory is cleaned up at startup.

It can change varaibles of PGC_USERSET by non-superuser or
PGC_SUSET by superuser. The local session user should have the
same privileges with pg_signal_backend() on the target session.

This patch contains documentation but doesn't contain test yet.

I would appreciate any comments or suggestions on this.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
From 03c53ca5259c2cc4b66c6339ea494de9016d9263 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Tue, 29 Jan 2019 17:03:57 +0900
Subject: [PATCH] ALTER SESSION

Some tracking features are controled by GUC so they cannot be
activated from another session. The ALTER SESSION command allows
sessions to change some of GUC settings of another session.
---
 doc/src/sgml/config.sgml             |  26 ++-
 doc/src/sgml/ref/allfiles.sgml       |   1 +
 doc/src/sgml/reference.sgml          |   1 +
 src/backend/nodes/copyfuncs.c        |  14 ++
 src/backend/nodes/equalfuncs.c       |  12 ++
 src/backend/parser/gram.y            |  47 ++++-
 src/backend/postmaster/postmaster.c  |   6 +
 src/backend/replication/basebackup.c |   3 +
 src/backend/tcop/utility.c           |  13 ++
 src/backend/utils/init/postinit.c    |   3 +
 src/backend/utils/misc/guc-file.l    |  24 +++
 src/backend/utils/misc/guc.c         | 334 +++++++++++++++++++++++++++--------
 src/bin/initdb/initdb.c              |   3 +-
 src/include/nodes/nodes.h            |   1 +
 src/include/nodes/parsenodes.h       |  12 ++
 src/include/utils/guc.h              |  11 ++
 16 files changed, 433 insertions(+), 78 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index b6f5822b84..a52989342a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -175,6 +175,14 @@ shared_buffers = 128MB
      read whenever <filename>postgresql.conf</filename> is, and its settings take
      effect in the same way.  Settings in <filename>postgresql.auto.conf</filename>
      override those in <filename>postgresql.conf</filename>.
+
+    </para>
+    <para>
+     Furthermore a directory <filename>pg_session_conf</filename> in the
+     PostgreSQL data directory contains per-session configuration files. Every
+     file holds settings provided through the
+     <xref linkend="sql-altersession"/> command. This is read by reloading
+     after the two above files and removed at the session-end.
     </para>
 
     <para>
@@ -195,8 +203,8 @@ shared_buffers = 128MB
       The already-mentioned <xref linkend="sql-altersystem"/> command
       provides a SQL-accessible means of changing global defaults; it is
       functionally equivalent to editing <filename>postgresql.conf</filename>.
-      In addition, there are two commands that allow setting of defaults
-      on a per-database or per-role basis:
+      In addition, there are three commands that allow setting of defaults
+      on a per-database, per-role or per-session basis:
      </para>
 
      <itemizedlist>
@@ -213,6 +221,13 @@ shared_buffers = 128MB
        per-database settings to be overridden with user-specific values.
       </para>
      </listitem>
+
+     <listitem>
+      <para>
+       The <xref linkend="sql-altersession"/> command allows other sessions to
+        override session-local settings with user-specific values.
+      </para>
+     </listitem>
     </itemizedlist>
 
      <para>
@@ -223,6 +238,13 @@ shared_buffers = 128MB
       Note that some settings cannot be changed after server start, and
       so cannot be set with these commands (or the ones listed below).
     </para>
+     <para>
+      Values set with <command>ALTER SESSION</command> are applied only when
+      reloading.  They override values obtained from the configuration files
+      or server command line, and constitute defaults for the rest of the
+      session.  Note that it can change only settings that are changeable
+      on-session.
+    </para>
 
      <para>
       Once a client is connected to the database, <productname>PostgreSQL</productname>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index c81c87ef41..650ae49a24 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -34,6 +34,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 alterSession       SYSTEM "alter_session.sgml">
 <!ENTITY alterSubscription  SYSTEM "alter_subscription.sgml">
 <!ENTITY alterSystem        SYSTEM "alter_system.sgml">
 <!ENTITY alterStatistics    SYSTEM "alter_statistics.sgml">
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index db4f4167e3..85f515e2b2 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -62,6 +62,7 @@
    &alterSchema;
    &alterSequence;
    &alterServer;
+   &alterSession;
    &alterStatistics;
    &alterSubscription;
    &alterSystem;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3eb7e95d64..108bf6976b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3923,6 +3923,17 @@ _copyAlterSystemStmt(const AlterSystemStmt *from)
     return newnode;
 }
 
+static AlterSessionStmt *
+_copyAlterSessionStmt(const AlterSessionStmt *from)
+{
+    AlterSessionStmt *newnode = makeNode(AlterSessionStmt);
+
+    COPY_NODE_FIELD(sessionopt);
+    COPY_NODE_FIELD(setstmt);
+    COPY_SCALAR_FIELD(immediate);
+    return newnode;
+}
+
 static CreateSeqStmt *
 _copyCreateSeqStmt(const CreateSeqStmt *from)
 {
@@ -5341,6 +5352,9 @@ copyObjectImpl(const void *from)
         case T_AlterSystemStmt:
             retval = _copyAlterSystemStmt(from);
             break;
+        case T_AlterSessionStmt:
+            retval = _copyAlterSessionStmt(from);
+            break;
         case T_CreateSeqStmt:
             retval = _copyCreateSeqStmt(from);
             break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5c4fa7d077..34e13412a0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1732,6 +1732,15 @@ _equalAlterSystemStmt(const AlterSystemStmt *a, const AlterSystemStmt *b)
     return true;
 }
 
+static bool
+_equalAlterSessionStmt(const AlterSessionStmt *a, const AlterSessionStmt *b)
+{
+    COMPARE_NODE_FIELD(sessionopt);
+    COMPARE_NODE_FIELD(setstmt);
+    COMPARE_SCALAR_FIELD(immediate);
+
+    return true;
+}
 
 static bool
 _equalCreateSeqStmt(const CreateSeqStmt *a, const CreateSeqStmt *b)
@@ -3406,6 +3415,9 @@ equal(const void *a, const void *b)
         case T_AlterSystemStmt:
             retval = _equalAlterSystemStmt(a, b);
             break;
+        case T_AlterSessionStmt:
+            retval = _equalAlterSessionStmt(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 c1faf4152c..33d4922bfb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -248,8 +248,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
         AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
         AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
         AlterObjectDependsStmt AlterObjectSchemaStmt AlterOwnerStmt
-        AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
-        AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
+        AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterSessionStmt
+        AlterTableStmt AlterTblSpcStmt AlterExtensionStmt
+        AlterExtensionContentsStmt AlterForeignTableStmt
         AlterCompositeTypeStmt AlterUserMappingStmt
         AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
         AlterDefaultPrivilegesStmt DefACLAction
@@ -303,13 +304,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>    createdb_opt_item copy_opt_item
                 transaction_mode_item
                 create_extension_opt_item alter_extension_opt_item
+                altersess_option_item
 
 %type <ival>    opt_lock lock_type cast_context
 %type <ival>    vacuum_option_list vacuum_option_elem
                 analyze_option_list analyze_option_elem
 %type <boolean>    opt_or_replace
                 opt_grant_grant_option opt_grant_admin_option
-                opt_nowait opt_if_exists opt_with_data
+                opt_nowait opt_if_exists opt_with_data opt_immediate
 %type <ival>    opt_nowait_or_skip
 
 %type <list>    OptRoleList AlterOptRoleList
@@ -839,6 +841,7 @@ stmt :
             | AlterPolicyStmt
             | AlterSeqStmt
             | AlterSystemStmt
+            | AlterSessionStmt
             | AlterTableStmt
             | AlterTblSpcStmt
             | AlterCompositeTypeStmt
@@ -10169,6 +10172,44 @@ AlterSystemStmt:
         ;
 
 
+/*****************************************************************************
+ *
+ *        ALTER SESSION
+ *
+ * This is used to change configuration parameters persistently.
+ *****************************************************************************/
+
+AlterSessionStmt:
+            ALTER SESSION WITH '(' altersess_option_item ')' SET generic_set opt_immediate
+                {
+                    AlterSessionStmt *n = makeNode(AlterSessionStmt);
+                    n->sessionopt = $5;
+                    n->setstmt = $8;
+                    n->immediate = $9;
+                    $$ = (Node *)n;
+                }
+            | ALTER SESSION WITH '(' altersess_option_item ')' RESET generic_reset opt_immediate
+                {
+                    AlterSessionStmt *n = makeNode(AlterSessionStmt);
+                    n->sessionopt = $5;
+                    n->setstmt = $8;
+                    n->immediate = $9;
+                    $$ = (Node *)n;
+                }
+        ;
+
+altersess_option_item:
+        ColLabel Iconst
+        {
+            $$ = makeDefElem($1, (Node *)makeInteger($2), @1);
+        }
+    ;
+
+opt_immediate:
+        IMMEDIATE    { $$ = true; }
+        | /*EMPTY*/ { $$ = false; }
+    ;
+
 /*****************************************************************************
  *
  * Manipulate a domain
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3052bbbc21..1a32bb5f52 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -125,6 +125,7 @@
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/datetime.h"
+#include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/pidfile.h"
 #include "utils/ps_status.h"
@@ -1242,6 +1243,11 @@ PostmasterMain(int argc, char *argv[])
      */
     RemovePgTempFiles();
 
+    /*
+     * Remove old session configuration files.
+     */
+    CleanupSessionGUCfiles();
+    
     /*
      * Forcibly remove the files signaling a standby promotion request.
      * Otherwise, the existence of those files triggers a promotion too early,
diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c
index def6c03dd0..6631c8498b 100644
--- a/src/backend/replication/basebackup.c
+++ b/src/backend/replication/basebackup.c
@@ -153,6 +153,9 @@ static const char *excludeDirContents[] =
     /* Contents zeroed on startup, see StartupSUBTRANS(). */
     "pg_subtrans",
 
+    /* Contents removed on startup, see CleanupSessionGUCfiles(). */
+    "pg_session_conf",
+
     /* end of list */
     NULL
 };
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6ec795f1b4..3b78d6457d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -681,6 +681,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
             AlterSystemSetConfigFile((AlterSystemStmt *) parsetree);
             break;
 
+        case T_AlterSessionStmt:
+            PreventInTransactionBlock(isTopLevel, "ALTER SESSION");
+            AlterSessionSetConfigFile((AlterSessionStmt *) parsetree);
+            break;
+
         case T_VariableSetStmt:
             ExecSetVariableStmt((VariableSetStmt *) parsetree, isTopLevel);
             break;
@@ -2605,6 +2610,10 @@ CreateCommandTag(Node *parsetree)
             tag = "ALTER SYSTEM";
             break;
 
+        case T_AlterSessionStmt:
+            tag = "ALTER SESSION";
+            break;
+
         case T_VariableSetStmt:
             switch (((VariableSetStmt *) parsetree)->kind)
             {
@@ -3235,6 +3244,10 @@ GetCommandLogLevel(Node *parsetree)
             lev = LOGSTMT_DDL;
             break;
 
+        case T_AlterSessionStmt:
+            lev = LOGSTMT_DDL;
+            break;
+
         case T_VariableSetStmt:
             lev = LOGSTMT_ALL;
             break;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index c0b6231458..45c005a109 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -703,6 +703,9 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
      */
     before_shmem_exit(ShutdownPostgres, 0);
 
+    /* Session initialization for GUC system */
+    InitializeSessionGUC();
+
     /* The autovacuum launcher is done here */
     if (IsAutoVacuumLauncherProcess())
     {
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index 1c8b5f7d84..a577ed2def 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -208,6 +208,30 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
             ConfFileWithError = PG_AUTOCONF_FILENAME;
             goto bail_out;
         }
+
+        /* load session configuration if any */
+        if (MyBackendId != InvalidBackendId)
+        {
+            static char SessionConfFileName[MAXPGPATH];
+            struct stat st;
+
+            snprintf(SessionConfFileName, sizeof(SessionConfFileName),
+                     PG_SESSIONCONF_FILENAME, MyBackendId);
+
+            /* be silent on absence */
+            if (stat(SessionConfFileName, &st) == 0)
+            {
+                if (!ParseConfigFile(SessionConfFileName, true,
+                                     NULL, 0, 0, elevel,
+                                     &head, &tail))
+                {
+                    /* Syntax error(s) detected in the file, so bail out */
+                    error = true;
+                    ConfFileWithError = SessionConfFileName;
+                    goto bail_out;
+                }
+            }
+        }
     }
     else
     {
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c216ed0922..4b8fef5b00 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -36,6 +36,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
 #include "commands/async.h"
+#include "commands/defrem.h"
 #include "commands/prepare.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
@@ -72,9 +73,11 @@
 #include "storage/dsm_impl.h"
 #include "storage/standby.h"
 #include "storage/fd.h"
+#include "storage/ipc.h"
 #include "storage/large_object.h"
 #include "storage/pg_shmem.h"
 #include "storage/proc.h"
+#include "storage/procarray.h"
 #include "storage/predicate.h"
 #include "tcop/tcopprot.h"
 #include "tsearch/ts_cache.h"
@@ -4482,7 +4485,8 @@ 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 write_auto_conf_file(int fd, const char *filename, bool is_session,
+                                 ConfigVariable *head_p);
 static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
                           const char *name, const char *value);
 
@@ -4974,6 +4978,59 @@ guc_name_compare(const char *namea, const char *nameb)
     return 0;
 }
 
+void
+CleanupSessionGUCfiles(void)
+{
+    DIR           *dir;
+    struct dirent *entry;
+    char        fname[MAXPGPATH];
+
+    dir = AllocateDir(PG_SESSIONCONF_DIRNAME);
+    while ((entry = ReadDir(dir, PG_SESSIONCONF_DIRNAME)) != NULL)
+    {
+        if (strcmp(entry->d_name, ".") == 0 ||
+            strcmp(entry->d_name, "..") == 0)
+            continue;
+
+        snprintf(fname, sizeof(fname), "%s/%s", PG_SESSIONCONF_DIRNAME,
+                 entry->d_name);
+
+        /*
+         * no need for durable_unlnk here since this is done again in the next
+         * startup after a crash
+         */
+        if (unlink(fname) != 0)
+            ereport(NOTICE,
+                    (errcode_for_file_access(),
+                     errmsg("could not remove file \"%s\" during clean up of session config files: %m",
+                            fname)));
+    }
+    FreeDir(dir);
+}
+
+static void
+RemoveSessionGUCFile(int code, Datum arg)
+{
+    char SessionConfFileName[MAXPGPATH];
+
+    if (MyBackendId != InvalidBackendId)
+    {
+        snprintf(SessionConfFileName, sizeof(SessionConfFileName),
+                 PG_SESSIONCONF_FILENAME, MyBackendId);
+        unlink(SessionConfFileName);
+    }
+}
+
+void
+InitializeSessionGUC(void)
+{
+    if (!IsUnderPostmaster || MyBackendId == InvalidBackendId)
+        return;
+
+    RemoveSessionGUCFile(0, 0);
+    on_shmem_exit(RemoveSessionGUCFile, 0);
+}
+
 
 /*
  * Initialize GUC options during program startup.
@@ -7470,16 +7527,19 @@ flatten_set_variable_args(const char *name, List *args)
  * values before writing them.
  */
 static void
-write_auto_conf_file(int fd, const char *filename, ConfigVariable *head)
+write_auto_conf_file(int fd, const char *filename, bool is_session,
+                     ConfigVariable *head)
 {
     StringInfoData buf;
     ConfigVariable *item;
+    char *command_name = is_session ? "ALTER SESSION" : "ALTER_SYSTEM";
 
     initStringInfo(&buf);
 
     /* Emit file header containing warning comment */
     appendStringInfoString(&buf, "# Do not edit this file manually!\n");
-    appendStringInfoString(&buf, "# It will be overwritten by the ALTER SYSTEM command.\n");
+    appendStringInfo(&buf, "# It will be overwritten by the %s command.\n",
+        command_name);
 
     errno = 0;
     if (write(fd, buf.data, buf.len) != buf.len)
@@ -7599,103 +7659,101 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
 
 
 /*
- * Execute ALTER SYSTEM statement.
- *
- * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value,
- * and write out an updated file.  If the command is ALTER SYSTEM RESET ALL,
- * we can skip reading the old file and just write an empty file.
- *
- * An LWLock is used to serialize updates of the configuration file.
- *
- * In case of an error, we leave the original automatic
- * configuration file (PG_AUTOCONF_FILENAME) intact.
+  Check and process the given set statment of ALTER SYSTEM and ALTER SESSION.
  */
-void
-AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
+static void
+AlterConfigProcessSetCommand(VariableSetStmt *setstmt,
+                             bool is_session, bool is_superuser,
+                             char **name, char **value, bool *resetall)
 {
-    char       *name;
-    char       *value;
-    bool        resetall = false;
-    ConfigVariable *head = NULL;
-    ConfigVariable *tail = NULL;
-    volatile int Tmpfd;
-    char        AutoConfFileName[MAXPGPATH];
-    char        AutoConfTmpFileName[MAXPGPATH];
+    bool disallow_by_context = false;
 
-    if (!superuser())
-        ereport(ERROR,
-                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 (errmsg("must be superuser to execute ALTER SYSTEM command"))));
+    *name = setstmt->name;
 
-    /*
-     * Extract statement arguments
-     */
-    name = altersysstmt->setstmt->name;
-
-    switch (altersysstmt->setstmt->kind)
+    switch (setstmt->kind)
     {
         case VAR_SET_VALUE:
-            value = ExtractSetVariableArgs(altersysstmt->setstmt);
+            *value = ExtractSetVariableArgs(setstmt);
             break;
 
         case VAR_SET_DEFAULT:
         case VAR_RESET:
-            value = NULL;
+            *value = NULL;
             break;
 
         case VAR_RESET_ALL:
-            value = NULL;
-            resetall = true;
+            *value = NULL;
+            *resetall = true;
             break;
 
         default:
-            elog(ERROR, "unrecognized alter system stmt type: %d",
-                 altersysstmt->setstmt->kind);
+            elog(ERROR, "unrecognized set type: %d",
+                 setstmt->kind);
             break;
     }
 
     /*
      * Unless it's RESET_ALL, validate the target variable and value
      */
-    if (!resetall)
+    if (!*resetall)
     {
         struct config_generic *record;
 
-        record = find_option(name, false, ERROR);
+        record = find_option(*name, false, ERROR);
         if (record == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_UNDEFINED_OBJECT),
                      errmsg("unrecognized configuration parameter \"%s\"",
-                            name)));
+                            *name)));
 
         /*
          * Don't allow parameters that can't be set in configuration files to
          * be set in PG_AUTOCONF_FILENAME file.
          */
-        if ((record->context == PGC_INTERNAL) ||
+
+        if (record->context == PGC_INTERNAL)
+            disallow_by_context = true;
+        else if (is_session)
+        {
+            /* parameters that cannot be changed on-session */
+            if (record->context == PGC_SIGHUP ||
+                record->context == PGC_POSTMASTER ||
+                record->context == PGC_SU_BACKEND ||
+                record->context == PGC_BACKEND)
+                disallow_by_context = true;
+
+            /* parameters that can be changed only by superuser */
+            if (!is_superuser && record->context == PGC_SUSET)
+                ereport(ERROR,
+                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                         errmsg("permission denied to set parameter \"%s\"",
+                                *name)));
+        }
+
+        if (disallow_by_context ||
             (record->flags & GUC_DISALLOW_IN_FILE) ||
             (record->flags & GUC_DISALLOW_IN_AUTO_FILE))
             ereport(ERROR,
                     (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
                      errmsg("parameter \"%s\" cannot be changed",
-                            name)));
+                            *name)));
 
         /*
          * If a value is specified, verify that it's sane.
          */
-        if (value)
+        if (*value)
         {
             union config_var_val newval;
             void       *newextra = NULL;
 
             /* Check that it's acceptable for the indicated parameter */
-            if (!parse_and_validate_value(record, name, value,
+            if (!parse_and_validate_value(record, *name, *value,
                                           PGC_S_FILE, ERROR,
                                           &newval, &newextra))
                 ereport(ERROR,
                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                          errmsg("invalid value for parameter \"%s\": \"%s\"",
-                                name, value)));
+                                *name, *value)));
 
             if (record->vartype == PGC_STRING && newval.stringval != NULL)
                 free(newval.stringval);
@@ -7707,22 +7765,25 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
              * grammar for config files doesn't support embedded newlines in
              * string literals.
              */
-            if (strchr(value, '\n'))
+            if (strchr(*value, '\n'))
                 ereport(ERROR,
                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                         errmsg("parameter value for ALTER SYSTEM must not contain a newline")));
+                         errmsg("parameter value for SET must not contain a newline")));
         }
     }
+}
 
-    /*
-     * PG_AUTOCONF_FILENAME and its corresponding temporary file are always in
-     * the data directory, so we can reference them by simple relative paths.
-     */
-    snprintf(AutoConfFileName, sizeof(AutoConfFileName), "%s",
-             PG_AUTOCONF_FILENAME);
-    snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s",
-             AutoConfFileName,
-             "tmp");
+/*
+ * Edit or create auto configuration files.
+ */
+static void
+EditAutoConfigFile(char *confname, char *tmpname, bool is_session,
+                   char *name, char *value, bool resetall)
+{
+    struct stat st;
+    volatile int Tmpfd;
+    ConfigVariable *head = NULL;
+    ConfigVariable *tail = NULL;
 
     /*
      * Only one backend is allowed to operate on PG_AUTOCONF_FILENAME at a
@@ -7733,30 +7794,43 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 
     /*
      * If we're going to reset everything, then no need to open or parse the
-     * old file.  We'll just write out an empty list.
+     * old file.
+     */
+    if (is_session && resetall)
+    {
+        /* RESET ALL just remove files for session configuratrion */
+        if (stat(confname, &st) == 0)
+            durable_unlink(confname, ERROR);
+
+        unlink(tmpname);    /* ignore error */
+        LWLockRelease(AutoFileLock);
+        return;
+    }
+
+    /*
+     * We'll just write out an empty list for auto configration for RESET
+     * ALL
      */
     if (!resetall)
     {
-        struct stat st;
-
-        if (stat(AutoConfFileName, &st) == 0)
+        if (stat(confname, &st) == 0)
         {
-            /* open old file PG_AUTOCONF_FILENAME */
+            /* open old auto config file  */
             FILE       *infile;
 
-            infile = AllocateFile(AutoConfFileName, "r");
+            infile = AllocateFile(confname, "r");
             if (infile == NULL)
                 ereport(ERROR,
                         (errcode_for_file_access(),
                          errmsg("could not open file \"%s\": %m",
-                                AutoConfFileName)));
+                                confname)));
 
             /* parse it */
-            if (!ParseConfigFp(infile, AutoConfFileName, 0, LOG, &head, &tail))
+            if (!ParseConfigFp(infile, confname, 0, LOG, &head, &tail))
                 ereport(ERROR,
                         (errcode(ERRCODE_CONFIG_FILE_ERROR),
                          errmsg("could not parse contents of file \"%s\"",
-                                AutoConfFileName)));
+                                confname)));
 
             FreeFile(infile);
         }
@@ -7775,13 +7849,13 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
      * If there is a temp file left over due to a previous crash, it's okay to
      * truncate and reuse it.
      */
-    Tmpfd = BasicOpenFile(AutoConfTmpFileName,
+    Tmpfd = BasicOpenFile(tmpname,
                           O_CREAT | O_RDWR | O_TRUNC);
     if (Tmpfd < 0)
         ereport(ERROR,
                 (errcode_for_file_access(),
                  errmsg("could not open file \"%s\": %m",
-                        AutoConfTmpFileName)));
+                        tmpname)));
 
     /*
      * Use a TRY block to clean up the file if we fail.  Since we need a TRY
@@ -7790,7 +7864,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
     PG_TRY();
     {
         /* Write and sync the new contents to the temporary file */
-        write_auto_conf_file(Tmpfd, AutoConfTmpFileName, head);
+        write_auto_conf_file(Tmpfd, tmpname, is_session, head);
 
         /* Close before renaming; may be required on some platforms */
         close(Tmpfd);
@@ -7801,7 +7875,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
          * at worst it can lose the parameters set by last ALTER SYSTEM
          * command.
          */
-        durable_rename(AutoConfTmpFileName, AutoConfFileName, ERROR);
+        durable_rename(tmpname, confname, ERROR);
     }
     PG_CATCH();
     {
@@ -7810,7 +7884,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
             close(Tmpfd);
 
         /* Unlink, but ignore any error */
-        (void) unlink(AutoConfTmpFileName);
+        (void) unlink(tmpname);
 
         PG_RE_THROW();
     }
@@ -7821,6 +7895,122 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
     LWLockRelease(AutoFileLock);
 }
 
+/*
+ * Execute ALTER SYSTEM statement.
+ *
+ * Read the old PG_AUTOCONF_FILENAME file, merge in the new variable value,
+ * and write out an updated file.  If the command is ALTER SYSTEM RESET ALL,
+ * we can skip reading the old file and just write an empty file.
+ *
+ * An LWLock is used to serialize updates of the configuration file.
+ *
+ * In case of an error, we leave the original automatic
+ * configuration file (PG_AUTOCONF_FILENAME) intact.
+ */
+void
+AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
+{
+    char       *name;
+    char       *value;
+    bool        resetall = false;
+    char        AutoConfFileName[MAXPGPATH];
+    char        AutoConfTmpFileName[MAXPGPATH];
+
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("must be superuser to execute ALTER SYSTEM command"))));
+
+    /*
+     * Extract statement arguments
+     */
+
+    AlterConfigProcessSetCommand(altersysstmt->setstmt, false, false,
+                                 &name, &value, &resetall);
+    /*
+     * PG_AUTOCONF_FILENAME and its corresponding temporary file are always in
+     * the data directory, so we can reference them by simple relative paths.
+     */
+    snprintf(AutoConfFileName, sizeof(AutoConfFileName), "%s",
+             PG_AUTOCONF_FILENAME);
+    snprintf(AutoConfTmpFileName, sizeof(AutoConfTmpFileName), "%s.%s",
+             AutoConfFileName,
+             "tmp");
+
+    EditAutoConfigFile(AutoConfFileName, AutoConfTmpFileName, false,
+                       name, value, resetall);
+}
+
+/*
+ * Execute ALTER SESSION statement.
+ *
+ * Peforms the similar thing with ALTER SYSTEM to PG_SESSIONCONF_FILENAME.
+ */
+void
+AlterSessionSetConfigFile(AlterSessionStmt *altersesstmt)
+{
+    int            target_pid;
+    PGPROC       *target_proc;
+    DefElem       *def;
+    char       *name;
+    char       *value;
+    bool        resetall = false;
+    char        SessionConfigFile[MAXPGPATH];
+    char        SessionConfigTmpFile[MAXPGPATH];
+
+    def = altersesstmt->sessionopt;
+
+    /* ignoring namespace */
+    if (strcmp(def->defname, "pid") != 0)
+        ereport(ERROR,
+                (errmsg ("only pid is allowed here")));
+    target_pid = defGetInt32(def);
+
+    target_proc = BackendPidGetProc(target_pid);
+
+    if (target_proc == NULL)
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                 errmsg("PID %d is not a PostgreSQL server process",
+                        target_pid)));
+
+    /* The same condition to pg_signal_backend() */
+    if ((superuser_arg(target_proc->roleId) && !superuser()) ||
+        (!has_privs_of_role(GetUserId(), target_proc->roleId) &&
+         !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID)))
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("insufficient privileges for the session")));
+
+    if (target_proc->backendId == InvalidBackendId)
+        elog(ERROR, "not a backend");
+
+    AlterConfigProcessSetCommand(altersesstmt->setstmt, true, superuser(),
+                                 &name, &value, &resetall);
+
+    /* don't allow RESET ALL for non-superusers */
+    if (resetall && !superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("RESET ALL requres superuser privileges")));
+
+    /* Ok, edit or create session config file */
+    snprintf(SessionConfigFile, sizeof(SessionConfigFile),
+             PG_SESSIONCONF_FILENAME, target_proc->backendId);
+    snprintf(SessionConfigTmpFile, sizeof(SessionConfigTmpFile), "%s.%s",
+             SessionConfigFile, "tmp");
+
+    EditAutoConfigFile(SessionConfigFile, SessionConfigTmpFile, true,
+                       name, value, resetall);
+
+    if (altersesstmt->immediate)
+    {
+        if (kill(target_pid, SIGHUP))
+            ereport(WARNING,
+                    (errmsg("failed to send signal to session: %m")));
+    }
+}
+
 /*
  * SET command
  */
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index fd50a809ea..138190e9dc 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -221,7 +221,8 @@ static const char *const subdirs[] = {
     "pg_xact",
     "pg_logical",
     "pg_logical/snapshots",
-    "pg_logical/mappings"
+    "pg_logical/mappings",
+    "pg_session_conf"
 };
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 10dac60cd3..48ed5ab753 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -407,6 +407,7 @@ typedef enum NodeTag
     T_RefreshMatViewStmt,
     T_ReplicaIdentityStmt,
     T_AlterSystemStmt,
+    T_AlterSessionStmt,
     T_CreatePolicyStmt,
     T_AlterPolicyStmt,
     T_CreateTransformStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index addc2c2ec7..251a52b0cc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3118,6 +3118,18 @@ typedef struct AlterSystemStmt
     VariableSetStmt *setstmt;    /* SET subcommand */
 } AlterSystemStmt;
 
+/* ----------------------
+ *        Alter Session Statement
+ * ----------------------
+ */
+typedef struct AlterSessionStmt
+{
+    NodeTag        type;
+    DefElem       *sessionopt;        /* session property */
+    VariableSetStmt *setstmt;    /* SET subcommand */
+    bool        immediate;        /* reload immediately */
+} AlterSessionStmt;
+
 /* ----------------------
  *        Cluster Statement (support pbrown's cluster index implementation)
  * ----------------------
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index c07e7b945e..d8c779752b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -33,6 +33,14 @@
  */
 #define PG_AUTOCONF_FILENAME        "postgresql.auto.conf"
 
+/*
+ * Session configuration file name for ALTER SESSION.
+ * This file will be used to store values of configuration parameters
+ * set by ALTER SESSION command.
+ */
+#define PG_SESSIONCONF_DIRNAME    "pg_session_conf"
+#define PG_SESSIONCONF_FILENAME    PG_SESSIONCONF_DIRNAME"/postgresql.%d.conf"
+
 /*
  * Certain options can only be set at certain times. The rules are
  * like this:
@@ -351,6 +359,8 @@ extern const char *GetConfigOption(const char *name, bool missing_ok,
 extern const char *GetConfigOptionResetString(const char *name);
 extern int    GetConfigOptionFlags(const char *name, bool missing_ok);
 extern void ProcessConfigFile(GucContext context);
+extern void CleanupSessionGUCfiles(void);
+extern void InitializeSessionGUC(void);
 extern void InitializeGUCOptions(void);
 extern bool SelectConfigFiles(const char *userDoption, const char *progname);
 extern void ResetAllOptions(void);
@@ -367,6 +377,7 @@ extern int set_config_option(const char *name, const char *value,
                   GucAction action, bool changeVal, int elevel,
                   bool is_reload);
 extern void AlterSystemSetConfigFile(AlterSystemStmt *setstmt);
+extern void AlterSessionSetConfigFile(AlterSessionStmt *altersesstmt);
 extern char *GetConfigOptionByName(const char *name, const char **varname,
                       bool missing_ok);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
-- 
2.16.3


pgsql-hackers by date:

Previous
From: Amit Kapila
Date:
Subject: Re: WIP: Avoid creation of the free space map for small tables
Next
From: Masahiko Sawada
Date:
Subject: Re: WIP: Avoid creation of the free space map for small tables