Arrange for SET LOCAL's effects to persist until the end of the current top
authorTom Lane
Tue, 11 Sep 2007 00:06:42 +0000 (00:06 +0000)
committerTom Lane
Tue, 11 Sep 2007 00:06:42 +0000 (00:06 +0000)
transaction, unless rolled back or overridden by a SET clause for the same
variable attached to a surrounding function call.  Per discussion, these
seem the best semantics.  Note that this is an INCOMPATIBLE CHANGE: in 8.0
through 8.2, SET LOCAL's effects disappeared at subtransaction commit
(leading to behavior that made little sense at the SQL level).

I took advantage of the opportunity to rewrite and simplify the GUC variable
save/restore logic a little bit.  The old idea of a "tentative" value is gone;
it was a hangover from before we had a stack.  Also, we no longer need a stack
entry for every nesting level, but only for those in which a variable's value
actually changed.

16 files changed:
doc/src/sgml/ref/create_function.sgml
doc/src/sgml/ref/prepare_transaction.sgml
doc/src/sgml/ref/reset.sgml
doc/src/sgml/ref/set.sgml
doc/src/sgml/ref/show.sgml
src/backend/utils/adt/ri_triggers.c
src/backend/utils/fmgr/fmgr.c
src/backend/utils/init/miscinit.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/README
src/backend/utils/misc/guc-file.l
src/backend/utils/misc/guc.c
src/include/utils/guc.h
src/include/utils/guc_tables.h
src/test/regress/expected/guc.out
src/test/regress/sql/guc.sql

index b0cfe84db1c67ed14735b56ca270f02b2c997789..8c542982d52ba32733dd658ef13bd9394ec021e0 100644 (file)
@@ -1,5 +1,5 @@
 
 
 
@@ -475,11 +475,11 @@ CREATE FUNCTION foo(int, out text) ...
    
 
    
-    If any SET clauses are attached to a function, then
+    If a SET clause is attached to a function, then
     the effects of a SET LOCAL command executed inside the
-    function are restricted to the function: the configuration parameter's
-    value is restored at function exit.  This is true even for parameters
-    not mentioned in the SET clause(s).  However, an ordinary
+    function for the same variable are restricted to the function: the
+    configuration parameter's prior value is still restored at function exit.
+    However, an ordinary
     SET command (without LOCAL) overrides the
     SET clause, much as it would do for a previous SET
     LOCAL command: the effects of such a command will persist after
index 0c8293f0ec5908d626ad1a05cd3e325134a9a2a3..5874b7993033990ecd45999569fab20e342e2153 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -94,7 +94,8 @@ PREPARE TRANSACTION transaction_id
   
 
   
-   If the transaction modified any run-time parameters with SET,
+   If the transaction modified any run-time parameters with SET
+   (without the LOCAL option),
    those effects persist after PREPARE TRANSACTION, and will not
    be affected by any later COMMIT PREPARED or 
    ROLLBACK PREPARED.  Thus, in this one respect
index 355de8912873e57fcb2575eb6799225ca9b17837..c90843ff583cfc7a2e60777b1b0a679cae0fa5cd 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -44,13 +44,16 @@ SET configuration_parameter TO DEFA
    have had, if no SET had ever been issued for it in the
    current session.  The actual source of this value might be a
    compiled-in default, the configuration file, command-line options,
-   or per-database or per-user default settings.  See 
-   linkend="runtime-config"> for details.
+   or per-database or per-user default settings.  This is subtly different
+   from defining it as the value that the parameter had at session
+   start, because if the value came from the configuration file, it
+   will be reset to whatever is specified by the configuration file now.
+   See  for details.
   
 
   
-   See the SET reference page for details on the
-   transaction behavior of RESET.
+   The transactional behavior of RESET is the same as
+   SET: its effects will be undone by transaction rollback.
   
  
 
@@ -62,8 +65,9 @@ SET configuration_parameter TO DEFA
     configuration_parameter
     
      
-      The name of a run-time parameter. See 
-      endterm="sql-set-title"> for a list.
+      Name of a settable run-time parameter.  Available parameters are
+      documented in  and on the
+       reference page.
      
     
    
@@ -83,9 +87,9 @@ SET configuration_parameter TO DEFA
   Examples
 
   
-   Set the geqo configuration variable to its default value:
+   Set the timezone configuration variable to its default value:
 
-RESET geqo;
+RESET timezone;
 
   
  
@@ -97,4 +101,13 @@ RESET geqo;
    RESET is a PostgreSQL extension.
   
  
+
+  See Also
+
+  
+   
+   
+  
 
index 76e8decf5c5708126e9b093a850704c95a7883b0..26ee8594b447ea76f606abc06efdd6694ebf8d04 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -40,13 +40,10 @@ SET [ SESSION | LOCAL ] TIME ZONE { timezone
   
 
   
-   If SET or SET SESSION is issued
-   within a transaction that is later aborted, the effects of the
+   If SET (or equivalently SET SESSION)
+   is issued within a transaction that is later aborted, the effects of the
    SET command disappear when the transaction is rolled
-   back.  (This behavior represents a change from
-   PostgreSQL versions prior to 7.3, where
-   the effects of SET would not roll back after a later
-   error.)  Once the surrounding transaction is committed, the effects
+   back.  Once the surrounding transaction is committed, the effects
    will persist until the end of the session, unless overridden by another
    SET.
   
@@ -59,6 +56,36 @@ SET [ SESSION | LOCAL ] TIME ZONE { timezone
    seen until the end of the transaction, but afterwards (if the transaction
    is committed) the SET value will take effect.
   
+
+  
+   The effects of SET or SET LOCAL are
+   also canceled by rolling back to a savepoint that is earlier than the
+   command.
+  
+
+  
+   If SET LOCAL is used within a function that has a
+   SET option for the same variable (see
+   ),
+   the effects of the SET LOCAL command disappear at
+   function exit; that is, the value in effect when the function was called is
+   restored anyway.  This allows SET LOCAL to be used for
+   dynamic or repeated changes of a parameter within a function, while still
+   having the convenience of using the SET option to save and
+   restore the caller's value.  However, a regular SET command
+   overrides any surrounding function's SET option; its effects
+   will persist unless rolled back.
+  
+
+  
+   
+    In PostgreSQL versions 8.0 through 8.2,
+    the effects of a SET LOCAL would be canceled by
+    releasing an earlier savepoint, or by successful exit from a
+    PL/pgSQL exception block.  This behavior
+    has been changed because it was deemed unintuitive.
+   
+  
  
 
  
@@ -106,8 +133,11 @@ SET [ SESSION | LOCAL ] TIME ZONE { timezone
      
       New value of parameter.  Values can be specified as string
       constants, identifiers, numbers, or comma-separated lists of
-      these.  DEFAULT can be used to specify
-      resetting the parameter to its default value.
+      these, as appropriate for the particular parameter.
+      DEFAULT can be written to specify
+      resetting the parameter to its default value (that is, whatever
+      value it would have had if no SET had been executed
+      in the current session).
      
     
    
@@ -200,7 +230,9 @@ SELECT setseed(value);
          DEFAULT
          
           
-           Set the time zone to your local time zone (the one that
+           Set the time zone to your local time zone (that is, the
+           server's default value of timezone; if this
+           has not been explicitly set anywhere, it will be the zone that
            the server's operating system defaults to).
           
          
@@ -221,7 +253,10 @@ SELECT setseed(value);
 
   
    The function set_config provides equivalent
-   functionality. See .
+   functionality; see .
+   Also, it is possible to UPDATE the
+   pg_settings
+   system view to perform the equivalent of SET.
   
  
  
index f43a0a50bd82c5fd2491d4dcca4cc711a5e1a71f..ab64f859428b24b2b137046208f25a580e69821b 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -36,7 +36,7 @@ SHOW ALL
    the PGOPTIONS environmental variable (when using
    libpq or a libpq-based
    application), or through command-line flags when starting the
-   postgres.  See 
+   postgres server.  See 
    linkend="runtime-config"> for details.
   
  
@@ -130,7 +130,11 @@ SHOW ALL
 
   
    The function current_setting produces
-   equivalent output. See .
+   equivalent output; see .
+   Also, the
+   pg_settings
+   system view produces the same information.
+
   
  
 
index 9add8f934d853b741d7d877c3ec65f471fde2532..e3a01ed76a901765db7d465c89777194c1acb6fe 100644 (file)
@@ -15,7 +15,7 @@
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.96 2007/08/15 19:15:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.97 2007/09/11 00:06:42 tgl Exp $
  *
  * ----------
  */
@@ -2749,7 +2749,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
    snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem);
    (void) set_config_option("work_mem", workmembuf,
                             PGC_USERSET, PGC_S_SESSION,
-                            true, true);
+                            GUC_ACTION_LOCAL, true);
 
    if (SPI_connect() != SPI_OK_CONNECT)
        elog(ERROR, "SPI_connect failed");
@@ -2832,13 +2832,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 
    /*
     * Restore work_mem for the remainder of the current transaction. This is
-    * another SET LOCAL, so it won't affect the session value, nor any
-    * tentative value if there is one.
+    * another SET LOCAL, so it won't affect the session value.
     */
    snprintf(workmembuf, sizeof(workmembuf), "%d", old_work_mem);
    (void) set_config_option("work_mem", workmembuf,
                             PGC_USERSET, PGC_S_SESSION,
-                            true, true);
+                            GUC_ACTION_LOCAL, true);
 
    return true;
 }
index f2d49eaf5360f2c72c29b8e308fff88c61136080..ea5cd4bf2dfe75dcfe127576b6368ca34f519589 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.109 2007/09/03 00:39:18 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.110 2007/09/11 00:06:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -925,11 +925,10 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
        if (fcache->proconfig)
        {
-           /* The options are processed as if by SET LOCAL var = val */
            ProcessGUCArray(fcache->proconfig,
                            (superuser() ? PGC_SUSET : PGC_USERSET),
                            PGC_S_SESSION,
-                           true);
+                           GUC_ACTION_SAVE);
        }
 
        result = FunctionCallInvoke(fcinfo);
@@ -937,8 +936,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
    PG_CATCH();
    {
        fcinfo->flinfo = save_flinfo;
-       if (fcache->proconfig)
-           AtEOXact_GUC(false, save_nestlevel);
+       /* We don't need to restore GUC settings, outer xact abort will */
        if (OidIsValid(fcache->userid))
            SetUserId(save_userid);
        PG_RE_THROW();
index 599fc9938b609fcfa9dfe6a42f5216cadfc04899..1da93dd7a28f14b2874fb283cdafb0d5b96cba29 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.163 2007/09/03 00:39:18 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.164 2007/09/11 00:06:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -453,7 +453,7 @@ InitializeSessionUserId(const char *rolename)
         * right to insert an option into pg_authid was checked when it was
         * inserted.
         */
-       ProcessGUCArray(a, PGC_SUSET, PGC_S_USER, false);
+       ProcessGUCArray(a, PGC_SUSET, PGC_S_USER, GUC_ACTION_SET);
    }
 
    ReleaseSysCache(roleTup);
index 965e94a53876488d03a67b963649db89db490365..d1bc2af876d7d006ddf15021ca350097c3cbd805 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.177 2007/09/03 00:39:18 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.178 2007/09/11 00:06:42 tgl Exp $
  *
  *
  *-------------------------------------------------------------------------
@@ -255,7 +255,7 @@ CheckMyDatabase(const char *name, bool am_superuser)
             * right to insert an option into pg_database was checked when it
             * was inserted.
             */
-           ProcessGUCArray(a, PGC_SUSET, PGC_S_DATABASE, false);
+           ProcessGUCArray(a, PGC_SUSET, PGC_S_DATABASE, GUC_ACTION_SET);
        }
    }
 
index bf1e3b654555452ecb565fb26dc169d87e694cda..263f97790f7795287284ba3d5e929011b3a501b1 100644 (file)
@@ -1,4 +1,4 @@
-$PostgreSQL: pgsql/src/backend/utils/misc/README,v 1.6 2007/09/10 00:57:21 tgl Exp $
+$PostgreSQL: pgsql/src/backend/utils/misc/README,v 1.7 2007/09/11 00:06:42 tgl Exp $
 
 
 GUC IMPLEMENTATION NOTES
@@ -69,74 +69,133 @@ by SHOW.
 
 SAVING/RESTORING GUC VARIABLE VALUES
 
-Prior values of configuration variables must be remembered in order to
-deal with three special cases: RESET (a/k/a SET TO DEFAULT), rollback of
-SET on transaction abort, and rollback of SET LOCAL at transaction end
-(either commit or abort).  RESET is defined as selecting the value that
-would be effective had there never been any SET commands in the current
-session.
+Prior values of configuration variables must be remembered in order to deal
+with several special cases: RESET (a/k/a SET TO DEFAULT), rollback of SET
+on transaction abort, rollback of SET LOCAL at transaction end (either
+commit or abort), and save/restore around a function that has a SET option.
+RESET is defined as selecting the value that would be effective had there
+never been any SET commands in the current session.
 
 To handle these cases we must keep track of many distinct values for each
 variable.  The primary values are:
 
 * actual variable contents always the current effective value
 
-* reset_value          the value to use for RESET
+* reset_val            the value to use for RESET
+
+(Each GUC entry also has a boot_val which is the wired-in default value.
+This is assigned to the reset_val and the actual variable during
+InitializeGUCOptions().  The boot_val is also consulted to restore the
+correct reset_val if SIGHUP processing discovers that a variable formerly
+specified in postgresql.conf is no longer set there.)
+
+In addition to the primary values, there is a stack of former effective
+values that might need to be restored in future.  Stacking and unstacking
+is controlled by the GUC "nest level", which is zero when outside any
+transaction, one at top transaction level, and incremented for each
+open subtransaction or function call with a SET option.  A stack entry
+is made whenever a GUC variable is first modified at a given nesting level.
+(Note: the reset_val need not be stacked because it is only changed by
+non-transactional operations.)
+
+A stack entry has a state, a prior value of the GUC variable, a remembered
+source of that prior value, and depending on the state may also have a
+"masked" value.  The masked value is needed when SET followed by SET LOCAL
+occur at the same nest level: the SET's value is masked but must be
+remembered to restore after transaction commit.
+
+During initialization we set the actual value and reset_val based on
+whichever non-interactive source has the highest priority.  They will
+have the same value.
 
-* tentative_value      the uncommitted result of SET
+The possible transactional operations on a GUC value are:
 
-The reason we need a tentative_value separate from the actual value is
-that when a transaction does SET followed by SET LOCAL, the actual value
-will now be the LOCAL value, but we want to remember the prior SET so that
-that value is restored at transaction commit.
+Entry to a function with a SET option:
 
-In addition, for each level of transaction (possibly nested) we have to
-remember the transaction-entry-time actual and tentative values, in case
-we need to restore them at transaction end.  (The RESET value is essentially
-non-transactional, so it doesn't have to be stacked.)  For efficiency these
-stack entries are not constructed until/unless the variable is actually SET
-within a particular transaction.
+   Push a stack entry with the prior variable value and state SAVE,
+   then set the variable.
 
-During initialization we set the actual value and reset_value based on
-whichever non-interactive source has the highest priority.  They will
-have the same value.  The tentative_value is not meaningful at this point.
+Plain SET command:
+
+   If no stack entry of current level:
+       Push new stack entry w/prior value and state SET
+   else if stack entry's state is SAVE, SET, or LOCAL:
+       change stack state to SET, don't change saved value
+       (here we are forgetting effects of prior set action)
+   else (entry must have state SET+LOCAL):
+       discard its masked value, change state to SET
+       (here we are forgetting effects of prior SET and SET LOCAL)
+   Now set new value.
+
+SET LOCAL command:
+
+   If no stack entry of current level:
+       Push new stack entry w/prior value and state LOCAL
+   else if stack entry's state is SAVE or LOCAL or SET+LOCAL:
+       no change to stack entry
+       (in SAVE case, SET LOCAL will be forgotten at func exit)
+   else (entry must have state SET):
+       put current active into its masked slot, set state SET+LOCAL
+   Now set new value.
+
+Transaction or subtransaction abort:
+
+   Pop stack entries, restoring prior value, until top < subxact depth
 
-A SET command starts by stacking the existing actual and tentative values
-if this hasn't already been done within the current transaction.  Then:
+Transaction or subtransaction commit (incl. successful function exit):
 
-A SET LOCAL command sets the actual variable (and nothing else).  At
-transaction end, the stacked values are used to restore the GUC entry
-to its pre-transaction state.
+   While stack entry level >= subxact depth
 
-A SET (or SET SESSION) command sets the actual variable, and if no error,
-then sets the tentative_value.  If the transaction commits, the
-tentative_value is assigned again to the actual variable (which could by
-now be different, if the SET was followed by SET LOCAL).  If the
-transaction aborts, the stacked values are used to restore the GUC entry
-to its pre-transaction state.
+       if entry's state is SAVE:
+           pop, restoring prior value
+       else if level is 1 and entry's state is SET+LOCAL:
+           pop, restoring *masked* value
+       else if level is 1 and entry's state is SET:
+           pop, discarding old value
+       else if level is 1 and entry's state is LOCAL:
+           pop, restoring prior value
+       else if there is no entry of exactly level N-1:
+           decrement entry's level, no other state change
+       else
+           merge entries of level N-1 and N as specified below
 
-In the case of SET within nested subtransactions, at each commit the
-tentative_value propagates out to the next transaction level.  It will
-be thrown away at abort of any level, or after exiting the top transaction.
+The merged entry will have level N-1 and prior = older prior, so easiest
+to keep older entry and free newer.  There are 12 possibilities since
+we already handled level N state = SAVE:
 
-RESET is executed like a SET, but using the reset_value as the desired new
+N-1        N
+
+SAVE       SET     discard top prior, set state SET
+SAVE       LOCAL       discard top prior, no change to stack entry
+SAVE       SET+LOCAL   discard top prior, copy masked, state S+L
+
+SET        SET     discard top prior, no change to stack entry
+SET        LOCAL       copy top prior to masked, state S+L
+SET        SET+LOCAL   discard top prior, copy masked, state S+L
+
+LOCAL      SET     discard top prior, set state SET
+LOCAL      LOCAL       discard top prior, no change to stack entry
+LOCAL      SET+LOCAL   discard top prior, copy masked, state S+L
+
+SET+LOCAL  SET     discard top prior and second masked, state SET
+SET+LOCAL  LOCAL       discard top prior, no change to stack entry
+SET+LOCAL  SET+LOCAL   discard top prior, copy masked, state S+L
+
+
+RESET is executed like a SET, but using the reset_val as the desired new
 value.  (We do not provide a RESET LOCAL command, but SET LOCAL TO DEFAULT
 has the same behavior that RESET LOCAL would.)  The source associated with
-the reset_value also becomes associated with the actual and tentative values.
+the reset_val also becomes associated with the actual value.
 
 If SIGHUP is received, the GUC code rereads the postgresql.conf
 configuration file (this does not happen in the signal handler, but at
 next return to main loop; note that it can be executed while within a
 transaction).  New values from postgresql.conf are assigned to actual
-variable, reset_value, and stacked actual values, but only if each of
+variable, reset_val, and stacked actual values, but only if each of
 these has a current source priority <= PGC_S_FILE.  (It is thus possible
-for reset_value to track the config-file setting even if there is
+for reset_val to track the config-file setting even if there is
 currently a different interactive value of the actual variable.)
 
-Note that tentative_value is unused and undefined except between a SET
-command and the end of the transaction.  Also notice that we must track
-the source associated with each one of the values.
-
 The assign_hook and show_hook routines work only with the actual variable,
 and are not directly aware of the additional values maintained by GUC.
 This is not a problem for normal usage, since we can assign first to the
@@ -154,9 +213,9 @@ pstrdup/palloc mechanisms.  We would need to keep them in a permanent
 context anyway, and strdup gives us more control over handling
 out-of-memory failures.
 
-We allow a string variable's actual value, reset_val, tentative_val, and
-stacked copies of same to point at the same storage.  This makes it
-slightly harder to free space (must test whether a value to be freed isn't
-equal to any of the other pointers in the GUC entry or associated stack
-items).  The main advantage is that we never need to strdup during
-transaction commit/abort, so cannot cause an out-of-memory failure there.
+We allow a string variable's actual value, reset_val, boot_val, and stacked
+values to point at the same storage.  This makes it slightly harder to free
+space (we must test whether a value to be freed isn't equal to any of the
+other pointers in the GUC entry or associated stack items).  The main
+advantage is that we never need to strdup during transaction commit/abort,
+so cannot cause an out-of-memory failure there.
index 124dfbf62e1570d23a584819e8b84f849308a6cd..1d454053ce812ed923e1830a3dcd3b1458af0a4d 100644 (file)
@@ -4,7 +4,7 @@
  *
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/misc/guc-file.l,v 1.51 2007/09/10 00:57:21 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/misc/guc-file.l,v 1.52 2007/09/11 00:06:42 tgl Exp $
  */
 
 %{
@@ -231,7 +231,7 @@ ProcessConfigFile(GucContext context)
        }
 
        if (!set_config_option(item->name, item->value, context,
-                              PGC_S_FILE, false, false))
+                              PGC_S_FILE, GUC_ACTION_SET, false))
            goto cleanup_list;
    }
 
@@ -264,24 +264,21 @@ ProcessConfigFile(GucContext context)
 
        /*
         * Reset any "file" sources to "default", else set_config_option
-        * will not override those settings.  tentative_source should
-        * never be "file".
+        * will not override those settings.
         */
        if (gconf->reset_source == PGC_S_FILE)
            gconf->reset_source = PGC_S_DEFAULT;
-       Assert(gconf->tentative_source != PGC_S_FILE);
        if (gconf->source == PGC_S_FILE)
            gconf->source = PGC_S_DEFAULT;
        for (stack = gconf->stack; stack; stack = stack->prev)
        {
-           Assert(stack->tentative_source != PGC_S_FILE);
            if (stack->source == PGC_S_FILE)
                stack->source = PGC_S_DEFAULT;
        }
 
        /* Now we can re-apply the wired-in default */
        set_config_option(gconf->name, NULL, context, PGC_S_DEFAULT,
-                         false, true);
+                         GUC_ACTION_SET, true);
    }
 
    /*
@@ -289,25 +286,27 @@ ProcessConfigFile(GucContext context)
     * is a no-op except in the case where one of these had been in the
     * config file and is now removed.  PGC_S_ENV_VAR will override the
     * wired-in default we just applied, but cannot override any other source.
-    * PGPORT can be ignored, because it cannot be changed without restart.
+    *
     * Keep this list in sync with InitializeGUCOptions()!
+    * PGPORT can be ignored, because it cannot be changed without restart.
+    * We assume rlimit hasn't changed, either.
     */
    envvar = getenv("PGDATESTYLE");
    if (envvar != NULL)
        set_config_option("datestyle", envvar, PGC_POSTMASTER,
-                         PGC_S_ENV_VAR, false, true);
+                         PGC_S_ENV_VAR, GUC_ACTION_SET, true);
 
    envvar = getenv("PGCLIENTENCODING");
    if (envvar != NULL)
        set_config_option("client_encoding", envvar, PGC_POSTMASTER,
-                         PGC_S_ENV_VAR, false, true);
+                         PGC_S_ENV_VAR, GUC_ACTION_SET, true);
 
 
    /* If we got here all the options checked out okay, so apply them. */
    for (item = head; item; item = item->next)
    {
        set_config_option(item->name, item->value, context,
-                         PGC_S_FILE, false, true);
+                         PGC_S_FILE, GUC_ACTION_SET, true);
    }
 
  cleanup_list:
index 678dd20331531a6b986626e0cfd14db36c770fba..371bc0b000f6d3497f01f190f946af526e1e80d9 100644 (file)
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut .
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.419 2007/09/10 02:01:19 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.420 2007/09/11 00:06:42 tgl Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -422,9 +422,9 @@ const char *const config_type_names[] =
  * 4. Add a record below.
  *
  * 5. Add it to src/backend/utils/misc/postgresql.conf.sample, if
- *   appropriate
+ *   appropriate.
  *
- * 6. Don't forget to document the option.
+ * 6. Don't forget to document the option (at least in config.sgml).
  *
  * 7. If it's a new GUC_LIST option you must edit pg_dumpall.c to ensure
  *   it is not single quoted at dump time.
@@ -2499,7 +2499,7 @@ static int    GUCNestLevel = 0;   /* 1 when in main transaction */
 
 static int guc_var_compare(const void *a, const void *b);
 static int guc_name_compare(const char *namea, const char *nameb);
-static void push_old_value(struct config_generic * gconf);
+static void push_old_value(struct config_generic * gconf, GucAction action);
 static void ReportGUCOption(struct config_generic * record);
 static void ShowGUCConfigOption(const char *name, DestReceiver *dest);
 static void ShowAllGUCConfig(DestReceiver *dest);
@@ -2568,13 +2568,12 @@ set_string_field(struct config_string * conf, char **field, char *newval)
    if (oldval == NULL ||
        oldval == *(conf->variable) ||
        oldval == conf->reset_val ||
-       oldval == conf->tentative_val ||
        oldval == conf->boot_val)
        return;
    for (stack = conf->gen.stack; stack; stack = stack->prev)
    {
-       if (oldval == stack->tentative_val.stringval ||
-           oldval == stack->value.stringval)
+       if (oldval == stack->prior.stringval ||
+           oldval == stack->masked.stringval)
            return;
    }
 
@@ -2592,19 +2591,71 @@ string_field_used(struct config_string * conf, char *strval)
 
    if (strval == *(conf->variable) ||
        strval == conf->reset_val ||
-       strval == conf->tentative_val ||
        strval == conf->boot_val)
        return true;
    for (stack = conf->gen.stack; stack; stack = stack->prev)
    {
-       if (strval == stack->tentative_val.stringval ||
-           strval == stack->value.stringval)
+       if (strval == stack->prior.stringval ||
+           strval == stack->masked.stringval)
            return true;
    }
    return false;
 }
 
+/*
+ * Support for copying a variable's active value into a stack entry
+ */
+static void
+set_stack_value(struct config_generic * gconf, union config_var_value * val)
+{
+   switch (gconf->vartype)
+   {
+       case PGC_BOOL:
+           val->boolval =
+               *((struct config_bool *) gconf)->variable;
+           break;
+       case PGC_INT:
+           val->intval =
+               *((struct config_int *) gconf)->variable;
+           break;
+       case PGC_REAL:
+           val->realval =
+               *((struct config_real *) gconf)->variable;
+           break;
+       case PGC_STRING:
+           /* we assume stringval is NULL if not valid */
+           set_string_field((struct config_string *) gconf,
+                            &(val->stringval),
+                            *((struct config_string *) gconf)->variable);
+           break;
+   }
+}
+
+/*
+ * Support for discarding a no-longer-needed value in a stack entry
+ */
+static void
+discard_stack_value(struct config_generic *gconf, union config_var_value *val)
+{
+   switch (gconf->vartype)
+   {
+       case PGC_BOOL:
+       case PGC_INT:
+       case PGC_REAL:
+           /* no need to do anything */
+           break;
+       case PGC_STRING:
+           set_string_field((struct config_string *) gconf,
+                            &(val->stringval),
+                            NULL);
+           break;
+   }
+}
+
 
+/*
+ * Fetch the sorted array pointer (exported for help_config.c's use ONLY)
+ */
 struct config_generic **
 get_guc_variables(void)
 {
@@ -2878,7 +2929,9 @@ guc_var_compare(const void *a, const void *b)
    return guc_name_compare(confa->name, confb->name);
 }
 
-
+/*
+ * the bare comparison function for GUC names
+ */
 static int
 guc_name_compare(const char *namea, const char *nameb)
 {
@@ -2941,7 +2994,6 @@ InitializeGUCOptions(void)
 
        gconf->status = 0;
        gconf->reset_source = PGC_S_DEFAULT;
-       gconf->tentative_source = PGC_S_DEFAULT;
        gconf->source = PGC_S_DEFAULT;
        gconf->stack = NULL;
 
@@ -2994,7 +3046,6 @@ InitializeGUCOptions(void)
 
                    *conf->variable = NULL;
                    conf->reset_val = NULL;
-                   conf->tentative_val = NULL;
 
                    if (conf->boot_val == NULL)
                    {
@@ -3260,7 +3311,7 @@ ResetAllOptions(void)
            continue;
 
        /* Save old value to support transaction abort */
-       push_old_value(gconf);
+       push_old_value(gconf, GUC_ACTION_SET);
 
        switch (gconf->vartype)
        {
@@ -3273,11 +3324,7 @@ ResetAllOptions(void)
                                                   PGC_S_SESSION))
                            elog(ERROR, "failed to reset %s", conf->gen.name);
                    *conf->variable = conf->reset_val;
-                   conf->tentative_val = conf->reset_val;
                    conf->gen.source = conf->gen.reset_source;
-                   conf->gen.tentative_source = conf->gen.reset_source;
-                   conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   guc_dirty = true;
                    break;
                }
            case PGC_INT:
@@ -3289,11 +3336,7 @@ ResetAllOptions(void)
                                                   PGC_S_SESSION))
                            elog(ERROR, "failed to reset %s", conf->gen.name);
                    *conf->variable = conf->reset_val;
-                   conf->tentative_val = conf->reset_val;
                    conf->gen.source = conf->gen.reset_source;
-                   conf->gen.tentative_source = conf->gen.reset_source;
-                   conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   guc_dirty = true;
                    break;
                }
            case PGC_REAL:
@@ -3305,11 +3348,7 @@ ResetAllOptions(void)
                                                   PGC_S_SESSION))
                            elog(ERROR, "failed to reset %s", conf->gen.name);
                    *conf->variable = conf->reset_val;
-                   conf->tentative_val = conf->reset_val;
                    conf->gen.source = conf->gen.reset_source;
-                   conf->gen.tentative_source = conf->gen.reset_source;
-                   conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   guc_dirty = true;
                    break;
                }
            case PGC_STRING:
@@ -3338,11 +3377,7 @@ ResetAllOptions(void)
                    }
 
                    set_string_field(conf, conf->variable, str);
-                   set_string_field(conf, &conf->tentative_val, str);
                    conf->gen.source = conf->gen.reset_source;
-                   conf->gen.tentative_source = conf->gen.reset_source;
-                   conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   guc_dirty = true;
                    break;
                }
        }
@@ -3355,84 +3390,84 @@ ResetAllOptions(void)
 
 /*
  * push_old_value
- *     Push previous state during first assignment to a GUC variable
- *     within a particular transaction.
- *
- * We have to be willing to "back-fill" the state stack if the first
- * assignment occurs within a subtransaction nested several levels deep.
- * This ensures that if an intermediate transaction aborts, it will have
- * the proper value available to restore the setting to.
+ *     Push previous state during transactional assignment to a GUC variable.
  */
 static void
-push_old_value(struct config_generic * gconf)
+push_old_value(struct config_generic * gconf, GucAction action)
 {
    GucStack   *stack;
 
-   /* If we're not inside a transaction, do nothing */
+   /* If we're not inside a nest level, do nothing */
    if (GUCNestLevel == 0)
        return;
 
-   for (;;)
+   /* Do we already have a stack entry of the current nest level? */
+   stack = gconf->stack;
+   if (stack && stack->nest_level >= GUCNestLevel)
    {
-       /* Done if we already pushed it at this nesting depth */
-       if (gconf->stack && gconf->stack->nest_level >= GUCNestLevel)
-           return;
-
-       /*
-        * We keep all the stack entries in TopTransactionContext so as to
-        * avoid allocation problems when a subtransaction back-fills stack
-        * entries for upper transaction levels.
-        */
-       stack = (GucStack *) MemoryContextAlloc(TopTransactionContext,
-                                               sizeof(GucStack));
-
-       stack->prev = gconf->stack;
-       stack->nest_level = stack->prev ? stack->prev->nest_level + 1 : 1;
-       stack->status = gconf->status;
-       stack->tentative_source = gconf->tentative_source;
-       stack->source = gconf->source;
-
-       switch (gconf->vartype)
+       /* Yes, so adjust its state if necessary */
+       Assert(stack->nest_level == GUCNestLevel);
+       switch (action)
        {
-           case PGC_BOOL:
-               stack->tentative_val.boolval =
-                   ((struct config_bool *) gconf)->tentative_val;
-               stack->value.boolval =
-                   *((struct config_bool *) gconf)->variable;
-               break;
-
-           case PGC_INT:
-               stack->tentative_val.intval =
-                   ((struct config_int *) gconf)->tentative_val;
-               stack->value.intval =
-                   *((struct config_int *) gconf)->variable;
+           case GUC_ACTION_SET:
+               /* SET overrides any prior action at same nest level */
+               if (stack->state == GUC_SET_LOCAL)
+               {
+                   /* must discard old masked value */
+                   discard_stack_value(gconf, &stack->masked);
+               }
+               stack->state = GUC_SET;
                break;
-
-           case PGC_REAL:
-               stack->tentative_val.realval =
-                   ((struct config_real *) gconf)->tentative_val;
-               stack->value.realval =
-                   *((struct config_real *) gconf)->variable;
+           case GUC_ACTION_LOCAL:
+               if (stack->state == GUC_SET)
+               {
+                   /* SET followed by SET LOCAL, remember SET's value */
+                   set_stack_value(gconf, &stack->masked);
+                   stack->state = GUC_SET_LOCAL;
+               }
+               /* in all other cases, no change to stack entry */
                break;
-
-           case PGC_STRING:
-               stack->tentative_val.stringval =
-                   ((struct config_string *) gconf)->tentative_val;
-               stack->value.stringval =
-                   *((struct config_string *) gconf)->variable;
+           case GUC_ACTION_SAVE:
+               /* Could only have a prior SAVE of same variable */
+               Assert(stack->state == GUC_SAVE);
                break;
        }
+       Assert(guc_dirty);      /* must be set already */
+       return;
+   }
 
-       gconf->stack = stack;
-
-       /* Set state to indicate nothing happened yet within this level */
-       gconf->status = GUC_HAVE_STACK;
+   /*
+    * Push a new stack entry
+    *
+    * We keep all the stack entries in TopTransactionContext for simplicity.
+    */
+   stack = (GucStack *) MemoryContextAllocZero(TopTransactionContext,
+                                               sizeof(GucStack));
 
-       /* Ensure we remember to pop at end of xact */
-       guc_dirty = true;
+   stack->prev = gconf->stack;
+   stack->nest_level = GUCNestLevel;
+   switch (action)
+   {
+       case GUC_ACTION_SET:
+           stack->state = GUC_SET;
+           break;
+       case GUC_ACTION_LOCAL:
+           stack->state = GUC_LOCAL;
+           break;
+       case GUC_ACTION_SAVE:
+           stack->state = GUC_SAVE;
+           break;
    }
+   stack->source = gconf->source;
+   set_stack_value(gconf, &stack->prior);
+
+   gconf->stack = stack;
+
+   /* Ensure we remember to pop at end of xact */
+   guc_dirty = true;
 }
 
+
 /*
  * Do GUC processing at main transaction start.
  */
@@ -3471,6 +3506,7 @@ NewGUCNestLevel(void)
 void
 AtEOXact_GUC(bool isCommit, int nestLevel)
 {
+   bool        still_dirty;
    int         i;
 
    Assert(nestLevel > 0 && nestLevel <= GUCNestLevel);
@@ -3482,246 +3518,236 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
        return;
    }
 
+   still_dirty = false;
    for (i = 0; i < num_guc_variables; i++)
    {
        struct config_generic *gconf = guc_variables[i];
-       int         my_status = gconf->status;
-       GucStack   *stack = gconf->stack;
-       bool        useTentative;
-       bool        changed;
+       GucStack   *stack;
 
        /*
-        * Skip if nothing's happened to this var in this transaction
+        * Process and pop each stack entry within the nest level.  To
+        * simplify fmgr_security_definer(), we allow failure exit from
+        * a function-with-SET-options to be recovered at the surrounding
+        * transaction or subtransaction abort; so there could be more than
+        * one stack entry to pop.
         */
-       if ((my_status & (GUC_HAVE_TENTATIVE |
-                         GUC_HAVE_LOCAL |
-                         GUC_HAVE_STACK)) == 0)
+       while ((stack = gconf->stack) != NULL &&
+              stack->nest_level >= nestLevel)
        {
-           Assert(stack == NULL);
-           continue;
-       }
-       /* Assert that we stacked old value before changing it */
-       Assert(stack != NULL && (my_status & GUC_HAVE_STACK));
-       /* However, the last change may have been at an outer xact level */
-       if (stack->nest_level < nestLevel)
-           continue;
-       Assert(stack->nest_level == nestLevel);
-
-       /*
-        * We will pop the stack entry.  Start by restoring outer xact status
-        * (since we may want to modify it below).  Be careful to use
-        * my_status to reference the inner xact status below this point...
-        */
-       gconf->status = stack->status;
-
-       /*
-        * We have two cases:
-        *
-        * If commit and HAVE_TENTATIVE, set actual value to tentative (this
-        * is to override a SET LOCAL if one occurred later than SET). We keep
-        * the tentative value and propagate HAVE_TENTATIVE to the parent
-        * status, allowing the SET's effect to percolate up. (But if we're
-        * exiting the outermost transaction, we'll drop the HAVE_TENTATIVE
-        * bit below.)
-        *
-        * Otherwise, we have a transaction that aborted or executed only SET
-        * LOCAL (or no SET at all).  In either case it should have no further
-        * effect, so restore both tentative and actual values from the stack
-        * entry.
-        */
+           GucStack   *prev = stack->prev;
+           bool        restorePrior = false;
+           bool        restoreMasked = false;
+           bool        changed;
 
-       useTentative = isCommit && (my_status & GUC_HAVE_TENTATIVE) != 0;
-       changed = false;
-
-       switch (gconf->vartype)
-       {
-           case PGC_BOOL:
+           /*
+            * In this next bit, if we don't set either restorePrior or
+            * restoreMasked, we must "discard" any unwanted fields of the
+            * stack entries to avoid leaking memory.  If we do set one of
+            * those flags, unused fields will be cleaned up after restoring.
+            */
+           if (!isCommit)          /* if abort, always restore prior value */
+               restorePrior = true;
+           else if (stack->state == GUC_SAVE)
+               restorePrior = true;
+           else if (stack->nest_level == 1)
+           {
+               /* transaction commit */
+               if (stack->state == GUC_SET_LOCAL)
+                   restoreMasked = true;
+               else if (stack->state == GUC_SET)
                {
-                   struct config_bool *conf = (struct config_bool *) gconf;
-                   bool        newval;
-                   GucSource   newsource;
+                   /* we keep the current active value */
+                   discard_stack_value(gconf, &stack->prior);
+               }
+               else        /* must be GUC_LOCAL */
+                   restorePrior = true;
+           }
+           else if (prev == NULL ||
+                    prev->nest_level < stack->nest_level - 1)
+           {
+               /* decrement entry's level and do not pop it */
+               stack->nest_level--;
+               continue;
+           }
+           else
+           {
+               /*
+                * We have to merge this stack entry into prev.
+                * See README for discussion of this bit.
+                */
+               switch (stack->state)
+               {
+                   case GUC_SAVE:
+                       Assert(false); /* can't get here */
+
+                   case GUC_SET:
+                       /* next level always becomes SET */
+                       discard_stack_value(gconf, &stack->prior);
+                       if (prev->state == GUC_SET_LOCAL)
+                           discard_stack_value(gconf, &prev->masked);
+                       prev->state = GUC_SET;
+                       break;
 
-                   if (useTentative)
-                   {
-                       newval = conf->tentative_val;
-                       newsource = conf->gen.tentative_source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   }
-                   else
-                   {
-                       newval = stack->value.boolval;
-                       newsource = stack->source;
-                       conf->tentative_val = stack->tentative_val.boolval;
-                       conf->gen.tentative_source = stack->tentative_source;
-                   }
+                   case GUC_LOCAL:
+                       if (prev->state == GUC_SET)
+                       {
+                           /* LOCAL migrates down */
+                           prev->masked = stack->prior;
+                           prev->state = GUC_SET_LOCAL;
+                       }
+                       else
+                       {
+                           /* else just forget this stack level */
+                           discard_stack_value(gconf, &stack->prior);
+                       }
+                       break;
 
-                   if (*conf->variable != newval)
-                   {
-                       if (conf->assign_hook)
-                           if (!(*conf->assign_hook) (newval,
-                                                      true, PGC_S_OVERRIDE))
-                               elog(LOG, "failed to commit %s",
-                                    conf->gen.name);
-                       *conf->variable = newval;
-                       changed = true;
-                   }
-                   conf->gen.source = newsource;
-                   break;
+                   case GUC_SET_LOCAL:
+                       /* prior state at this level no longer wanted */
+                       discard_stack_value(gconf, &stack->prior);
+                       /* copy down the masked state */
+                       if (prev->state == GUC_SET_LOCAL)
+                           discard_stack_value(gconf, &prev->masked);
+                       prev->masked = stack->masked;
+                       prev->state = GUC_SET_LOCAL;
+                       break;
                }
-           case PGC_INT:
-               {
-                   struct config_int *conf = (struct config_int *) gconf;
-                   int         newval;
-                   GucSource   newsource;
+           }
 
-                   if (useTentative)
-                   {
-                       newval = conf->tentative_val;
-                       newsource = conf->gen.tentative_source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                   }
-                   else
-                   {
-                       newval = stack->value.intval;
-                       newsource = stack->source;
-                       conf->tentative_val = stack->tentative_val.intval;
-                       conf->gen.tentative_source = stack->tentative_source;
-                   }
+           changed = false;
 
-                   if (*conf->variable != newval)
-                   {
-                       if (conf->assign_hook)
-                           if (!(*conf->assign_hook) (newval,
-                                                      true, PGC_S_OVERRIDE))
-                               elog(LOG, "failed to commit %s",
-                                    conf->gen.name);
-                       *conf->variable = newval;
-                       changed = true;
-                   }
-                   conf->gen.source = newsource;
-                   break;
+           if (restorePrior || restoreMasked)
+           {
+               /* Perform appropriate restoration of the stacked value */
+               union config_var_value newvalue;
+               GucSource   newsource;
+
+               if (restoreMasked)
+               {
+                   newvalue = stack->masked;
+                   newsource = PGC_S_SESSION;
                }
-           case PGC_REAL:
+               else
                {
-                   struct config_real *conf = (struct config_real *) gconf;
-                   double      newval;
-                   GucSource   newsource;
+                   newvalue = stack->prior;
+                   newsource = stack->source;
+               }
 
-                   if (useTentative)
+               switch (gconf->vartype)
+               {
+                   case PGC_BOOL:
                    {
-                       newval = conf->tentative_val;
-                       newsource = conf->gen.tentative_source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
+                       struct config_bool *conf = (struct config_bool *) gconf;
+                       bool        newval = newvalue.boolval;
+
+                       if (*conf->variable != newval)
+                       {
+                           if (conf->assign_hook)
+                               if (!(*conf->assign_hook) (newval,
+                                                          true, PGC_S_OVERRIDE))
+                                   elog(LOG, "failed to commit %s",
+                                        conf->gen.name);
+                           *conf->variable = newval;
+                           changed = true;
+                       }
+                       break;
                    }
-                   else
+                   case PGC_INT:
                    {
-                       newval = stack->value.realval;
-                       newsource = stack->source;
-                       conf->tentative_val = stack->tentative_val.realval;
-                       conf->gen.tentative_source = stack->tentative_source;
-                   }
+                       struct config_int *conf = (struct config_int *) gconf;
+                       int         newval = newvalue.intval;
 
-                   if (*conf->variable != newval)
-                   {
-                       if (conf->assign_hook)
-                           if (!(*conf->assign_hook) (newval,
-                                                      true, PGC_S_OVERRIDE))
-                               elog(LOG, "failed to commit %s",
-                                    conf->gen.name);
-                       *conf->variable = newval;
-                       changed = true;
+                       if (*conf->variable != newval)
+                       {
+                           if (conf->assign_hook)
+                               if (!(*conf->assign_hook) (newval,
+                                                          true, PGC_S_OVERRIDE))
+                                   elog(LOG, "failed to commit %s",
+                                        conf->gen.name);
+                           *conf->variable = newval;
+                           changed = true;
+                       }
+                       break;
                    }
-                   conf->gen.source = newsource;
-                   break;
-               }
-           case PGC_STRING:
-               {
-                   struct config_string *conf = (struct config_string *) gconf;
-                   char       *newval;
-                   GucSource   newsource;
-
-                   if (useTentative)
+                   case PGC_REAL:
                    {
-                       newval = conf->tentative_val;
-                       newsource = conf->gen.tentative_source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
+                       struct config_real *conf = (struct config_real *) gconf;
+                       double      newval = newvalue.realval;
+
+                       if (*conf->variable != newval)
+                       {
+                           if (conf->assign_hook)
+                               if (!(*conf->assign_hook) (newval,
+                                                          true, PGC_S_OVERRIDE))
+                                   elog(LOG, "failed to commit %s",
+                                        conf->gen.name);
+                           *conf->variable = newval;
+                           changed = true;
+                       }
+                       break;
                    }
-                   else
+                   case PGC_STRING:
                    {
-                       newval = stack->value.stringval;
-                       newsource = stack->source;
-                       set_string_field(conf, &conf->tentative_val,
-                                        stack->tentative_val.stringval);
-                       conf->gen.tentative_source = stack->tentative_source;
-                   }
+                       struct config_string *conf = (struct config_string *) gconf;
+                       char       *newval = newvalue.stringval;
 
-                   if (*conf->variable != newval)
-                   {
-                       if (conf->assign_hook && newval)
+                       if (*conf->variable != newval)
                        {
-                           const char *newstr;
-
-                           newstr = (*conf->assign_hook) (newval, true,
-                                                          PGC_S_OVERRIDE);
-                           if (newstr == NULL)
-                               elog(LOG, "failed to commit %s",
-                                    conf->gen.name);
-                           else if (newstr != newval)
+                           if (conf->assign_hook && newval)
                            {
-                               /*
-                                * If newval should now be freed, it'll be
-                                * taken care of below.
-                                *
-                                * See notes in set_config_option about
-                                * casting
-                                */
-                               newval = (char *) newstr;
+                               const char *newstr;
+
+                               newstr = (*conf->assign_hook) (newval, true,
+                                                              PGC_S_OVERRIDE);
+                               if (newstr == NULL)
+                                   elog(LOG, "failed to commit %s",
+                                        conf->gen.name);
+                               else if (newstr != newval)
+                               {
+                                   /*
+                                    * If newval should now be freed, it'll be
+                                    * taken care of below.
+                                    *
+                                    * See notes in set_config_option about
+                                    * casting
+                                    */
+                                   newval = (char *) newstr;
+                               }
                            }
-                       }
 
-                       set_string_field(conf, conf->variable, newval);
-                       changed = true;
+                           set_string_field(conf, conf->variable, newval);
+                           changed = true;
+                       }
+                       /*
+                        * Release stacked values if not used anymore.
+                        * We could use discard_stack_value() here, but since
+                        * we have type-specific code anyway, might as well
+                        * inline it.
+                        */
+                       set_string_field(conf, &stack->prior.stringval, NULL);
+                       set_string_field(conf, &stack->masked.stringval, NULL);
+                       break;
                    }
-                   conf->gen.source = newsource;
-                   /* Release stacked values if not used anymore */
-                   set_string_field(conf, &stack->value.stringval,
-                                    NULL);
-                   set_string_field(conf, &stack->tentative_val.stringval,
-                                    NULL);
-                   /* Don't store tentative value separately after commit */
-                   if (nestLevel == 1)
-                       set_string_field(conf, &conf->tentative_val, NULL);
-                   break;
                }
-       }
 
-       /* Finish popping the state stack */
-       gconf->stack = stack->prev;
-       pfree(stack);
+               gconf->source = newsource;
+           }
 
-       /*
-        * If we're now out of all xact levels, forget TENTATIVE status bit;
-        * there's nothing tentative about the value anymore.
-        */
-       if (nestLevel == 1)
-       {
-           Assert(gconf->stack == NULL);
-           gconf->status = 0;
-       }
+           /* Finish popping the state stack */
+           gconf->stack = prev;
+           pfree(stack);
 
-       /* Report new value if we changed it */
-       if (changed && (gconf->flags & GUC_REPORT))
-           ReportGUCOption(gconf);
+           /* Report new value if we changed it */
+           if (changed && (gconf->flags & GUC_REPORT))
+               ReportGUCOption(gconf);
+       } /* end of stack-popping loop */
+
+       if (stack != NULL)
+           still_dirty = true;
    }
 
-   /*
-    * If we're now out of all xact levels, we can clear guc_dirty. (Note: we
-    * cannot reset guc_dirty when exiting a subtransaction, because we know
-    * that all outer transaction levels will have stacked values to deal
-    * with.)
-    */
-   if (nestLevel == 1)
-       guc_dirty = false;
+   /* If there are no remaining stack entries, we can reset guc_dirty */
+   guc_dirty = still_dirty;
 
    /* Update nesting level */
    GUCNestLevel = nestLevel - 1;
@@ -4123,8 +4149,13 @@ call_string_assign_hook(GucStringAssignHook assign_hook,
  * function is being called so it can apply the access restrictions
  * properly.
  *
- * If value is NULL, set the option to its default value. If the
- * parameter changeVal is false then don't really set the option but do all
+ * If value is NULL, set the option to its default value (normally the
+ * reset_val, but if source == PGC_S_DEFAULT we instead use the boot_val).
+ *
+ * action indicates whether to set the value globally in the session, locally
+ * to the current top transaction, or just for the duration of a function call.
+ *
+ * If changeVal is false then don't really set the option but do all
  * the checks to see if it would work.
  *
  * If there is an error (non-existing option, invalid value) then an
@@ -4141,7 +4172,7 @@ call_string_assign_hook(GucStringAssignHook assign_hook,
 bool
 set_config_option(const char *name, const char *value,
                  GucContext context, GucSource source,
-                 bool isLocal, bool changeVal)
+                 GucAction action, bool changeVal)
 {
    struct config_generic *record;
    int         elevel;
@@ -4306,9 +4337,6 @@ set_config_option(const char *name, const char *value,
 
    /*
     * Evaluate value and set variable.
-    *
-    * Note: if value == NULL then we are supposed to set to the reset_val,
-    * except when source == PGC_S_DEFAULT; then we set to the boot_val.
     */
    switch (record->vartype)
    {
@@ -4350,7 +4378,7 @@ set_config_option(const char *name, const char *value,
                {
                    /* Save old value to support transaction abort */
                    if (!makeDefault)
-                       push_old_value(&conf->gen);
+                       push_old_value(&conf->gen, action);
                    if (changeVal)
                    {
                        *conf->variable = newval;
@@ -4369,23 +4397,11 @@ set_config_option(const char *name, const char *value,
                        {
                            if (stack->source <= source)
                            {
-                               stack->value.boolval = newval;
+                               stack->prior.boolval = newval;
                                stack->source = source;
                            }
                        }
                    }
-                   else if (isLocal)
-                   {
-                       conf->gen.status |= GUC_HAVE_LOCAL;
-                       guc_dirty = true;
-                   }
-                   else
-                   {
-                       conf->tentative_val = newval;
-                       conf->gen.tentative_source = source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                       guc_dirty = true;
-                   }
                }
                break;
            }
@@ -4439,7 +4455,7 @@ set_config_option(const char *name, const char *value,
                {
                    /* Save old value to support transaction abort */
                    if (!makeDefault)
-                       push_old_value(&conf->gen);
+                       push_old_value(&conf->gen, action);
                    if (changeVal)
                    {
                        *conf->variable = newval;
@@ -4458,23 +4474,11 @@ set_config_option(const char *name, const char *value,
                        {
                            if (stack->source <= source)
                            {
-                               stack->value.intval = newval;
+                               stack->prior.intval = newval;
                                stack->source = source;
                            }
                        }
                    }
-                   else if (isLocal)
-                   {
-                       conf->gen.status |= GUC_HAVE_LOCAL;
-                       guc_dirty = true;
-                   }
-                   else
-                   {
-                       conf->tentative_val = newval;
-                       conf->gen.tentative_source = source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                       guc_dirty = true;
-                   }
                }
                break;
            }
@@ -4525,7 +4529,7 @@ set_config_option(const char *name, const char *value,
                {
                    /* Save old value to support transaction abort */
                    if (!makeDefault)
-                       push_old_value(&conf->gen);
+                       push_old_value(&conf->gen, action);
                    if (changeVal)
                    {
                        *conf->variable = newval;
@@ -4544,23 +4548,11 @@ set_config_option(const char *name, const char *value,
                        {
                            if (stack->source <= source)
                            {
-                               stack->value.realval = newval;
+                               stack->prior.realval = newval;
                                stack->source = source;
                            }
                        }
                    }
-                   else if (isLocal)
-                   {
-                       conf->gen.status |= GUC_HAVE_LOCAL;
-                       guc_dirty = true;
-                   }
-                   else
-                   {
-                       conf->tentative_val = newval;
-                       conf->gen.tentative_source = source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                       guc_dirty = true;
-                   }
                }
                break;
            }
@@ -4653,7 +4645,7 @@ set_config_option(const char *name, const char *value,
                {
                    /* Save old value to support transaction abort */
                    if (!makeDefault)
-                       push_old_value(&conf->gen);
+                       push_old_value(&conf->gen, action);
                    if (changeVal)
                    {
                        set_string_field(conf, conf->variable, newval);
@@ -4672,7 +4664,7 @@ set_config_option(const char *name, const char *value,
                        {
                            if (stack->source <= source)
                            {
-                               set_string_field(conf, &stack->value.stringval,
+                               set_string_field(conf, &stack->prior.stringval,
                                                 newval);
                                stack->source = source;
                            }
@@ -4681,18 +4673,6 @@ set_config_option(const char *name, const char *value,
                        if (newval && !string_field_used(conf, newval))
                            free(newval);
                    }
-                   else if (isLocal)
-                   {
-                       conf->gen.status |= GUC_HAVE_LOCAL;
-                       guc_dirty = true;
-                   }
-                   else
-                   {
-                       set_string_field(conf, &conf->tentative_val, newval);
-                       conf->gen.tentative_source = source;
-                       conf->gen.status |= GUC_HAVE_TENTATIVE;
-                       guc_dirty = true;
-                   }
                }
                else if (newval)
                    free(newval);
@@ -4716,7 +4696,8 @@ void
 SetConfigOption(const char *name, const char *value,
                GucContext context, GucSource source)
 {
-   (void) set_config_option(name, value, context, source, false, true);
+   (void) set_config_option(name, value, context, source,
+                            GUC_ACTION_SET, true);
 }
 
 
@@ -4942,6 +4923,8 @@ flatten_set_variable_args(const char *name, List *args)
 void
 ExecSetVariableStmt(VariableSetStmt *stmt)
 {
+   GucAction action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET;
+
    switch (stmt->kind)
    {
        case VAR_SET_VALUE:
@@ -4950,7 +4933,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
                              ExtractSetVariableArgs(stmt),
                              (superuser() ? PGC_SUSET : PGC_USERSET),
                              PGC_S_SESSION,
-                             stmt->is_local,
+                             action,
                              true);
            break;
        case VAR_SET_MULTI:
@@ -5006,7 +4989,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt)
                              NULL,
                              (superuser() ? PGC_SUSET : PGC_USERSET),
                              PGC_S_SESSION,
-                             stmt->is_local,
+                             action,
                              true);
            break;
        case VAR_RESET_ALL:
@@ -5051,7 +5034,7 @@ SetPGVariable(const char *name, List *args, bool is_local)
                      argstring,
                      (superuser() ? PGC_SUSET : PGC_USERSET),
                      PGC_S_SESSION,
-                     is_local,
+                     is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
                      true);
 }
 
@@ -5095,7 +5078,7 @@ set_config_by_name(PG_FUNCTION_ARGS)
                      value,
                      (superuser() ? PGC_SUSET : PGC_USERSET),
                      PGC_S_SESSION,
-                     is_local,
+                     is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
                      true);
 
    /* get the new current value */
@@ -5190,7 +5173,7 @@ define_custom_variable(struct config_generic *variable)
    if (value)
        set_config_option(name, value,
                          pHolder->gen.context, pHolder->gen.source,
-                         false, true);
+                         GUC_ACTION_SET, true);
 
    /*
     * Free up as much as we conveniently can of the placeholder structure
@@ -5198,7 +5181,6 @@ define_custom_variable(struct config_generic *variable)
     */
    set_string_field(pHolder, pHolder->variable, NULL);
    set_string_field(pHolder, &pHolder->reset_val, NULL);
-   set_string_field(pHolder, &pHolder->tentative_val, NULL);
 
    free(pHolder);
 }
@@ -6145,7 +6127,7 @@ read_nondefault_variables(void)
            elog(FATAL, "invalid format of exec config params file");
 
        (void) set_config_option(varname, varvalue, record->context,
-                                varsource, false, true);
+                                varsource, GUC_ACTION_SET, true);
        free(varname);
        free(varvalue);
    }
@@ -6196,13 +6178,13 @@ ParseLongOption(const char *string, char **name, char **value)
 
 /*
  * Handle options fetched from pg_database.datconfig, pg_authid.rolconfig,
- * pg_proc.proconfig, etc.  Caller must specify proper context/source/local.
+ * pg_proc.proconfig, etc.  Caller must specify proper context/source/action.
  *
  * The array parameter must be an array of TEXT (it must not be NULL).
  */
 void
 ProcessGUCArray(ArrayType *array,
-               GucContext context, GucSource source, bool isLocal)
+               GucContext context, GucSource source, GucAction action)
 {
    int         i;
 
@@ -6242,7 +6224,7 @@ ProcessGUCArray(ArrayType *array,
            continue;
        }
 
-       (void) set_config_option(name, value, context, source, isLocal, true);
+       (void) set_config_option(name, value, context, source, action, true);
 
        free(name);
        if (value)
@@ -6269,7 +6251,7 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
    /* test if the option is valid */
    set_config_option(name, value,
                      superuser() ? PGC_SUSET : PGC_USERSET,
-                     PGC_S_TEST, false, false);
+                     PGC_S_TEST, GUC_ACTION_SET, false);
 
    /* convert name to canonical spelling, so we can use plain strcmp */
    (void) GetConfigOptionByName(name, &varname);
@@ -6347,7 +6329,7 @@ GUCArrayDelete(ArrayType *array, const char *name)
    /* test if the option is valid */
    set_config_option(name, NULL,
                      superuser() ? PGC_SUSET : PGC_USERSET,
-                     PGC_S_TEST, false, false);
+                     PGC_S_TEST, GUC_ACTION_SET, false);
 
    /* convert name to canonical spelling, so we can use plain strcmp */
    (void) GetConfigOptionByName(name, &varname);
index d8fafff55902222bac01e59a9115aa047b4717ab..6672c8d821be173291a245ea027733cdad10be05 100644 (file)
@@ -7,7 +7,7 @@
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  * Written by Peter Eisentraut .
  *
- * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.85 2007/09/03 18:46:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/guc.h,v 1.86 2007/09/11 00:06:42 tgl Exp $
  *--------------------------------------------------------------------
  */
 #ifndef GUC_H
@@ -100,6 +100,14 @@ typedef bool (*GucRealAssignHook) (double newval, bool doit, GucSource source);
 
 typedef const char *(*GucShowHook) (void);
 
+typedef enum
+{
+   /* Types of set_config_option actions */
+   GUC_ACTION_SET,             /* regular SET command */
+   GUC_ACTION_LOCAL,           /* SET LOCAL command */
+   GUC_ACTION_SAVE             /* function SET option */
+} GucAction;
+
 #define GUC_QUALIFIER_SEPARATOR '.'
 
 /* GUC vars that are actually declared in guc.c, rather than elsewhere */
@@ -196,7 +204,7 @@ extern void BeginReportingGUCOptions(void);
 extern void ParseLongOption(const char *string, char **name, char **value);
 extern bool set_config_option(const char *name, const char *value,
                  GucContext context, GucSource source,
-                 bool isLocal, bool changeVal);
+                 GucAction action, bool changeVal);
 extern char *GetConfigOptionByName(const char *name, const char **varname);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
 extern int GetNumConfigOptions(void);
@@ -209,7 +217,7 @@ extern void ExecSetVariableStmt(VariableSetStmt *stmt);
 extern char *ExtractSetVariableArgs(VariableSetStmt *stmt);
 
 extern void ProcessGUCArray(ArrayType *array,
-                       GucContext context, GucSource source, bool isLocal);
+               GucContext context, GucSource source, GucAction action);
 extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value);
 extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name);
 
index 0bc74ccdd0bd5238b94405768215dd07bed37761..866678b033ef2106bf2cb956d716ec90632de1bb 100644 (file)
@@ -7,7 +7,7 @@
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
- *   $PostgreSQL: pgsql/src/include/utils/guc_tables.h,v 1.34 2007/09/10 00:57:22 tgl Exp $
+ *   $PostgreSQL: pgsql/src/include/utils/guc_tables.h,v 1.35 2007/09/11 00:06:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,18 +79,27 @@ enum config_group
 };
 
 /*
- * Stack entry for saving the state of a variable prior to the current
- * transaction
+ * Stack entry for saving the state a variable had prior to an uncommitted
+ * transactional change
  */
+typedef enum
+{
+   /* This is almost GucAction, but we need a fourth state for SET+LOCAL */
+   GUC_SAVE,                   /* entry caused by function SET option */
+   GUC_SET,                    /* entry caused by plain SET command */
+   GUC_LOCAL,                  /* entry caused by SET LOCAL command */
+   GUC_SET_LOCAL               /* entry caused by SET then SET LOCAL */
+} GucStackState;
+
 typedef struct guc_stack
 {
    struct guc_stack *prev;     /* previous stack item, if any */
-   int         nest_level;     /* nesting depth of cur transaction */
-   int         status;         /* previous status bits, see below */
-   GucSource   tentative_source;       /* source of the tentative_value */
-   GucSource   source;         /* source of the actual value */
-   union config_var_value tentative_val;       /* previous tentative val */
-   union config_var_value value;       /* previous actual value */
+   int         nest_level;     /* nesting depth at which we made entry */
+   GucStackState state;        /* see enum above */
+   GucSource   source;         /* source of the prior value */
+   union config_var_value prior;   /* previous value of variable */
+   union config_var_value masked;  /* SET value in a GUC_SET_LOCAL entry */
+   /* masked value's source must be PGC_S_SESSION, so no need to store it */
 } GucStack;
 
 /*
@@ -113,9 +122,8 @@ struct config_generic
    enum config_type vartype;   /* type of variable (set only at startup) */
    int         status;         /* status bits, see below */
    GucSource   reset_source;   /* source of the reset_value */
-   GucSource   tentative_source;       /* source of the tentative_value */
    GucSource   source;         /* source of the current actual value */
-   GucStack   *stack;          /* stacked outside-of-transaction states */
+   GucStack   *stack;          /* stacked prior values */
 };
 
 /* bit values in flags field */
@@ -141,10 +149,7 @@ struct config_generic
 #define GUC_UNIT_TIME          0x7000  /* mask for MS, S, MIN */
 
 /* bit values in status field */
-#define GUC_HAVE_TENTATIVE 0x0001      /* tentative value is defined */
-#define GUC_HAVE_LOCAL     0x0002      /* a SET LOCAL has been executed */
-#define GUC_HAVE_STACK     0x0004      /* we have stacked prior value(s) */
-#define GUC_IS_IN_FILE     0x0008      /* found it in config file */
+#define GUC_IS_IN_FILE     0x0001      /* found it in config file */
 /*
  * Caution: the GUC_IS_IN_FILE bit is transient state for ProcessConfigFile.
  * Do not assume that its value represents useful information elsewhere.
@@ -163,7 +168,6 @@ struct config_bool
    GucShowHook show_hook;
    /* variable fields, initialized at runtime: */
    bool        reset_val;
-   bool        tentative_val;
 };
 
 struct config_int
@@ -178,7 +182,6 @@ struct config_int
    GucShowHook show_hook;
    /* variable fields, initialized at runtime: */
    int         reset_val;
-   int         tentative_val;
 };
 
 struct config_real
@@ -193,7 +196,6 @@ struct config_real
    GucShowHook show_hook;
    /* variable fields, initialized at runtime: */
    double      reset_val;
-   double      tentative_val;
 };
 
 struct config_string
@@ -206,7 +208,6 @@ struct config_string
    GucShowHook show_hook;
    /* variable fields, initialized at runtime: */
    char       *reset_val;
-   char       *tentative_val;
 };
 
 /* constant tables corresponding to enums above and in guc.h */
index 2276cff4e0c448fd7c78850a6e934393e74a30f6..2e6056b376962fa9d5ee897146396334aae15ca8 100644 (file)
@@ -352,6 +352,85 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
  2006-08-13 12:34:56-07
 (1 row)
 
+-- SET LOCAL persists through RELEASE (which was not true in 8.0-8.2)
+BEGIN;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 400ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle 
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+      timestamptz       
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 300;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 300ms
+(1 row)
+
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+   DateStyle   
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+         timestamptz          
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+RELEASE SAVEPOINT sp;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 300ms
+(1 row)
+
+SHOW datestyle;
+   DateStyle   
+---------------
+ Postgres, MDY
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+         timestamptz          
+------------------------------
+ Sun Aug 13 12:34:56 2006 PDT
+(1 row)
+
+ROLLBACK;
+SHOW vacuum_cost_delay;
+ vacuum_cost_delay 
+-------------------
+ 400ms
+(1 row)
+
+SHOW datestyle;
+ DateStyle 
+-----------
+ ISO, YMD
+(1 row)
+
+SELECT '2006-08-13 12:34:56'::timestamptz;
+      timestamptz       
+------------------------
+ 2006-08-13 12:34:56-07
+(1 row)
+
 -- SET followed by SET LOCAL
 BEGIN;
 SET vacuum_cost_delay TO 400;
@@ -558,3 +637,65 @@ select report_guc('regex_flavor'), current_setting('regex_flavor');
  advanced   | advanced
 (1 row)
 
+-- SET LOCAL is restricted by a function SET option
+create or replace function myfunc(int) returns text as $$
+begin
+  set local regex_flavor = extended;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+select myfunc(0), current_setting('regex_flavor');
+  myfunc  | current_setting 
+----------+-----------------
+ extended | advanced
+(1 row)
+
+alter function myfunc(int) reset all;
+select myfunc(0), current_setting('regex_flavor');
+  myfunc  | current_setting 
+----------+-----------------
+ extended | extended
+(1 row)
+
+set regex_flavor = advanced;
+-- but SET isn't
+create or replace function myfunc(int) returns text as $$
+begin
+  set regex_flavor = extended;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+select myfunc(0), current_setting('regex_flavor');
+  myfunc  | current_setting 
+----------+-----------------
+ extended | extended
+(1 row)
+
+set regex_flavor = advanced;
+-- it should roll back on error, though
+create or replace function myfunc(int) returns text as $$
+begin
+  set regex_flavor = extended;
+  perform 1/$1;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+select myfunc(0);
+ERROR:  division by zero
+CONTEXT:  SQL statement "SELECT  1/ $1 "
+PL/pgSQL function "myfunc" line 3 at PERFORM
+select current_setting('regex_flavor');
+ current_setting 
+-----------------
+ advanced
+(1 row)
+
+select myfunc(1), current_setting('regex_flavor');
+  myfunc  | current_setting 
+----------+-----------------
+ extended | extended
+(1 row)
+
index ffaddec7e988fec6248db673f10e38872512c901..e03bc2059575639ce1caf3370d7847f946ee3dce 100644 (file)
@@ -99,6 +99,26 @@ SHOW vacuum_cost_delay;
 SHOW datestyle;
 SELECT '2006-08-13 12:34:56'::timestamptz;
 
+-- SET LOCAL persists through RELEASE (which was not true in 8.0-8.2)
+BEGIN;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+SAVEPOINT sp;
+SET LOCAL vacuum_cost_delay TO 300;
+SHOW vacuum_cost_delay;
+SET LOCAL datestyle = 'Postgres, MDY';
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+RELEASE SAVEPOINT sp;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+ROLLBACK;
+SHOW vacuum_cost_delay;
+SHOW datestyle;
+SELECT '2006-08-13 12:34:56'::timestamptz;
+
 -- SET followed by SET LOCAL
 BEGIN;
 SET vacuum_cost_delay TO 400;
@@ -187,3 +207,47 @@ select report_guc('regex_flavor'), current_setting('regex_flavor');
 alter function report_guc(text) reset all;
 
 select report_guc('regex_flavor'), current_setting('regex_flavor');
+
+-- SET LOCAL is restricted by a function SET option
+create or replace function myfunc(int) returns text as $$
+begin
+  set local regex_flavor = extended;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+
+select myfunc(0), current_setting('regex_flavor');
+
+alter function myfunc(int) reset all;
+
+select myfunc(0), current_setting('regex_flavor');
+
+set regex_flavor = advanced;
+
+-- but SET isn't
+create or replace function myfunc(int) returns text as $$
+begin
+  set regex_flavor = extended;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+
+select myfunc(0), current_setting('regex_flavor');
+
+set regex_flavor = advanced;
+
+-- it should roll back on error, though
+create or replace function myfunc(int) returns text as $$
+begin
+  set regex_flavor = extended;
+  perform 1/$1;
+  return current_setting('regex_flavor');
+end $$
+language plpgsql
+set regex_flavor = basic;
+
+select myfunc(0);
+select current_setting('regex_flavor');
+select myfunc(1), current_setting('regex_flavor');