Re: nested xacts and phantom Xids

From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: Alvaro Herrera <alvherre(at)dcc(dot)uchile(dot)cl>
Cc: Patches <pgsql-patches(at)postgresql(dot)org>
Subject: Re: nested xacts and phantom Xids
Date: 2004-06-29 19:22:52
Message-ID: 6372.1088536972@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers pgsql-patches

Alvaro Herrera <alvherre(at)dcc(dot)uchile(dot)cl> writes:
> - GUC vars are rolled back on subxact abort

This did not work very well, but here is a revised GUC patch that I think
does work. It requires xact.c to export a function to report the
current nesting depth, and requires AtEOXact_GUC to be called in all
four cleanup paths (main and subxact commit and abort).

BTW, why do you have assign_transaction_read_only() in your patch? It
seems to me to be useful to create a readonly subxact of a read-write
outer transaction. Or is that just not-done-yet?

regards, tom lane

*** src/backend/utils/misc/README.orig Mon Jan 19 14:04:40 2004
--- src/backend/utils/misc/README Tue Jun 29 15:14:44 2004
***************
*** 68,116 ****
would be effective had there never been any SET commands in the current
session.

! To handle these cases we must keep track of as many as four distinct
! values for each variable. They are:

* actual variable contents always the current effective value

* reset_value the value to use for RESET

- * session_value the "committed" setting for the session
-
* tentative_value the uncommitted result of SET

! During initialization we set the first three of these (actual, reset_value,
! and session_value) based on whichever non-interactive source has the
! highest priority. All three will have the same value.

A SET LOCAL command sets the actual variable (and nothing else). At
! transaction end, the session_value is used to restore the actual variable
! to its pre-transaction value.

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 to the session_value and the actual variable
! (which could by now be different, if the SET was followed by SET LOCAL).
! If the transaction aborts, the tentative_value is discarded and the
! actual variable is restored from the session_value.

RESET is executed like a SET, but using the reset_value 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 session values.

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 session_value, 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 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 of the four 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.
--- 68,133 ----
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

* tentative_value the uncommitted result of SET

! 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.
!
! 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.
!
! 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.
!
! A SET command starts by stacking the existing actual and tentative values
! if this hasn't already been done within the current transaction. Then:

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.

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.
!
! 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.

RESET is executed like a SET, but using the reset_value 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.

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
! 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
! 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.
***************
*** 129,137 ****
context anyway, and strdup gives us more control over handling
out-of-memory failures.

! We allow a variable's actual value, reset_val, session_val, and
! tentative_val to point at the same storage. This makes it slightly harder
! to free space (must test that the value to be freed isn't equal to any of
! the other three pointers). The main advantage is that we never need to
! strdup during transaction commit/abort, so cannot cause an out-of-memory
! failure there.
--- 146,154 ----
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.
*** src/backend/utils/misc/guc.c.orig Sat Jun 12 14:22:41 2004
--- src/backend/utils/misc/guc.c Tue Jun 29 14:55:50 2004
***************
*** 16,26 ****
*/
#include "postgres.h"

! #include <errno.h>
#include <float.h>
#include <limits.h>
#include <unistd.h>
- #include <ctype.h>

#include "utils/guc.h"
#include "utils/guc_tables.h"
--- 16,25 ----
*/
#include "postgres.h"

! #include <ctype.h>
#include <float.h>
#include <limits.h>
#include <unistd.h>

#include "utils/guc.h"
#include "utils/guc_tables.h"
***************
*** 54,59 ****
--- 53,59 ----
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
+ #include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "pgstat.h"

***************
*** 105,110 ****
--- 105,111 ----
GucSource source);
static bool assign_stage_log_stats(bool newval, bool doit, GucSource source);
static bool assign_log_stats(bool newval, bool doit, GucSource source);
+ static bool assign_transaction_read_only(bool newval, bool doit, GucSource source);


/*
***************
*** 174,218 ****
static int block_size;
static bool integer_datetimes;

- /* Macros for freeing malloc'd pointers only if appropriate to do so */
- /* Some of these tests are probably redundant, but be safe ... */
- #define SET_STRING_VARIABLE(rec, newval) \
- do { \
- if (*(rec)->variable && \
- *(rec)->variable != (rec)->reset_val && \
- *(rec)->variable != (rec)->session_val && \
- *(rec)->variable != (rec)->tentative_val) \
- free(*(rec)->variable); \
- *(rec)->variable = (newval); \
- } while (0)
- #define SET_STRING_RESET_VAL(rec, newval) \
- do { \
- if ((rec)->reset_val && \
- (rec)->reset_val != *(rec)->variable && \
- (rec)->reset_val != (rec)->session_val && \
- (rec)->reset_val != (rec)->tentative_val) \
- free((rec)->reset_val); \
- (rec)->reset_val = (newval); \
- } while (0)
- #define SET_STRING_SESSION_VAL(rec, newval) \
- do { \
- if ((rec)->session_val && \
- (rec)->session_val != *(rec)->variable && \
- (rec)->session_val != (rec)->reset_val && \
- (rec)->session_val != (rec)->tentative_val) \
- free((rec)->session_val); \
- (rec)->session_val = (newval); \
- } while (0)
- #define SET_STRING_TENTATIVE_VAL(rec, newval) \
- do { \
- if ((rec)->tentative_val && \
- (rec)->tentative_val != *(rec)->variable && \
- (rec)->tentative_val != (rec)->reset_val && \
- (rec)->tentative_val != (rec)->session_val) \
- free((rec)->tentative_val); \
- (rec)->tentative_val = (newval); \
- } while (0)
-

/*
* Displayable names for context types (enum GucContext)
--- 175,180 ----
***************
*** 801,807 ****
GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
},
&XactReadOnly,
! false, NULL, NULL
},
{
{"add_missing_from", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
--- 763,769 ----
GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
},
&XactReadOnly,
! false, assign_transaction_read_only, NULL
},
{
{"add_missing_from", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
***************
*** 1766,1779 ****
*/
static struct config_generic **guc_variables;

! /* Current number of variables contained in the vector
! */
static int num_guc_variables;

! /* Vector capacity
! */
static int size_guc_variables;

static bool guc_dirty; /* TRUE if need to do commit/abort work */

static bool reporting_enabled; /* TRUE to enable GUC_REPORT */
--- 1728,1740 ----
*/
static struct config_generic **guc_variables;

! /* Current number of variables contained in the vector */
static int num_guc_variables;

! /* Vector capacity */
static int size_guc_variables;

+
static bool guc_dirty; /* TRUE if need to do commit/abort work */

static bool reporting_enabled; /* TRUE to enable GUC_REPORT */
***************
*** 1783,1796 ****

static int guc_var_compare(const void *a, const void *b);
static int guc_name_compare(const char *namea, const char *nameb);
static void ReportGUCOption(struct config_generic * record);
static char *_ShowOption(struct config_generic * record);

! struct config_generic** get_guc_variables()
{
return guc_variables;
}

/*
* Build the sorted array. This is split out so that it could be
* re-executed after startup (eg, we could allow loadable modules to
--- 1744,1814 ----

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 ReportGUCOption(struct config_generic * record);
static char *_ShowOption(struct config_generic * record);

!
! /*
! * Support for assigning to a field of a string GUC item. Free the prior
! * value if it's not referenced anywhere else in the item (including stacked
! * states).
! */
! static void
! set_string_field(struct config_string *conf, char **field, char *newval)
! {
! char *oldval = *field;
! GucStack *stack;
!
! /* Do the assignment */
! *field = newval;
!
! /* Exit if any duplicate references, or if old value was NULL anyway */
! if (oldval == NULL ||
! oldval == *(conf->variable) ||
! oldval == conf->reset_val ||
! oldval == conf->tentative_val)
! return;
! for (stack = conf->gen.stack; stack; stack = stack->prev)
! {
! if (oldval == stack->tentative_val.stringval ||
! oldval == stack->value.stringval)
! return;
! }
!
! /* Not used anymore, so free it */
! free(oldval);
! }
!
! /*
! * Detect whether strval is referenced anywhere in a GUC string item
! */
! static bool
! string_field_used(struct config_string *conf, char *strval)
! {
! GucStack *stack;
!
! if (strval == *(conf->variable) ||
! strval == conf->reset_val ||
! strval == conf->tentative_val)
! return true;
! for (stack = conf->gen.stack; stack; stack = stack->prev)
! {
! if (strval == stack->tentative_val.stringval ||
! strval == stack->value.stringval)
! return true;
! }
! return false;
! }
!
!
! struct config_generic **
! get_guc_variables(void)
{
return guc_variables;
}

+
/*
* Build the sorted array. This is split out so that it could be
* re-executed after startup (eg, we could allow loadable modules to
***************
*** 2001,2014 ****
return find_option(map_old_guc_names[i+1]);
}

! /* Check if the name is qualified, and if so, check if the qualifier
* maps to a custom variable class.
*/
dot = strchr(name, GUC_QUALIFIER_SEPARATOR);
if(dot != NULL && is_custom_class(name, dot - name))
! /*
! * Add a placeholder variable for this name
! */
return (struct config_generic*)add_placeholder_variable(name);

/* Unknown name */
--- 2019,2031 ----
return find_option(map_old_guc_names[i+1]);
}

! /*
! * Check if the name is qualified, and if so, check if the qualifier
* maps to a custom variable class.
*/
dot = strchr(name, GUC_QUALIFIER_SEPARATOR);
if(dot != NULL && is_custom_class(name, dot - name))
! /* Add a placeholder variable for this name */
return (struct config_generic*)add_placeholder_variable(name);

/* Unknown name */
***************
*** 2081,2089 ****

gconf->status = 0;
gconf->reset_source = PGC_S_DEFAULT;
- gconf->session_source = PGC_S_DEFAULT;
gconf->tentative_source = PGC_S_DEFAULT;
gconf->source = PGC_S_DEFAULT;

switch (gconf->vartype)
{
--- 2098,2106 ----

gconf->status = 0;
gconf->reset_source = PGC_S_DEFAULT;
gconf->tentative_source = PGC_S_DEFAULT;
gconf->source = PGC_S_DEFAULT;
+ gconf->stack = NULL;

switch (gconf->vartype)
{
***************
*** 2097,2103 ****
elog(FATAL, "failed to initialize %s to %d",
conf->gen.name, (int) conf->reset_val);
*conf->variable = conf->reset_val;
- conf->session_val = conf->reset_val;
break;
}
case PGC_INT:
--- 2114,2119 ----
***************
*** 2119,2125 ****
elog(FATAL, "failed to initialize %s to %d",
conf->gen.name, conf->reset_val);
*conf->variable = conf->reset_val;
- conf->session_val = conf->reset_val;
break;
}
case PGC_REAL:
--- 2135,2140 ----
***************
*** 2135,2141 ****
elog(FATAL, "failed to initialize %s to %g",
conf->gen.name, conf->reset_val);
*conf->variable = conf->reset_val;
- conf->session_val = conf->reset_val;
break;
}
case PGC_STRING:
--- 2150,2155 ----
***************
*** 2150,2156 ****
conf->assign_hook == assign_log_statement);
*conf->variable = NULL;
conf->reset_val = NULL;
- conf->session_val = NULL;
conf->tentative_val = NULL;

if (conf->boot_val == NULL)
--- 2164,2169 ----
***************
*** 2190,2196 ****
}
}
*conf->variable = str;
- conf->session_val = str;
break;
}
}
--- 2203,2208 ----
***************
*** 2254,2259 ****
--- 2266,2274 ----
if (gconf->source <= PGC_S_OVERRIDE)
continue;

+ /* Save old value to support transaction abort */
+ push_old_value(gconf);
+
switch (gconf->vartype)
{
case PGC_BOOL:
***************
*** 2336,2343 ****
}
}

! SET_STRING_VARIABLE(conf, str);
! SET_STRING_TENTATIVE_VAL(conf, str);
conf->gen.source = conf->gen.reset_source;
conf->gen.tentative_source = conf->gen.reset_source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
--- 2351,2358 ----
}
}

! 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;
***************
*** 2353,2363 ****


/*
! * Do GUC processing at transaction commit or abort.
*/
void
! AtEOXact_GUC(bool isCommit)
{
int i;

/* Quick exit if nothing's changed in this transaction */
--- 2368,2460 ----


/*
! * 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.
! */
! static void
! push_old_value(struct config_generic *gconf)
! {
! int my_level = GetCurrentTransactionNestLevel();
! GucStack *stack;
!
! /* If we're not inside a transaction, do nothing */
! if (my_level == 0)
! return;
!
! for (;;)
! {
! /* Done if we already pushed it at this nesting depth */
! if (gconf->stack && gconf->stack->nest_level >= my_level)
! 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)
! {
! 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;
! break;
!
! case PGC_REAL:
! stack->tentative_val.realval =
! ((struct config_real *) gconf)->tentative_val;
! stack->value.realval =
! *((struct config_real *) gconf)->variable;
! break;
!
! case PGC_STRING:
! stack->tentative_val.stringval =
! ((struct config_string *) gconf)->tentative_val;
! stack->value.stringval =
! *((struct config_string *) gconf)->variable;
! break;
! }
!
! gconf->stack = stack;
!
! /* Set state to indicate nothing happened yet within this level */
! gconf->status = GUC_HAVE_STACK;
!
! /* Ensure we remember to pop at end of xact */
! guc_dirty = true;
! }
! }
!
! /*
! * Do GUC processing at transaction or subtransaction commit or abort.
*/
void
! AtEOXact_GUC(bool isCommit, bool isSubXact)
{
+ int my_level;
int i;

/* Quick exit if nothing's changed in this transaction */
***************
*** 2371,2385 ****
guc_string_workspace = NULL;
}

for (i = 0; i < num_guc_variables; i++)
{
struct config_generic *gconf = guc_variables[i];
bool changed;

! /* Skip if nothing's happened to this var in this transaction */
! if (gconf->status == 0)
continue;

changed = false;

switch (gconf->vartype)
--- 2468,2523 ----
guc_string_workspace = NULL;
}

+ my_level = GetCurrentTransactionNestLevel();
+ Assert(isSubXact ? (my_level > 1) : (my_level == 1));
+
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;

! /*
! * Skip if nothing's happened to this var in this transaction
! */
! if (my_status == 0)
! {
! 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 < my_level)
continue;
+ Assert(stack->nest_level == my_level);

+ /*
+ * 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.
+ */
+
+ useTentative = isCommit && (my_status & GUC_HAVE_TENTATIVE) != 0;
changed = false;

switch (gconf->vartype)
***************
*** 2387,2512 ****
case PGC_BOOL:
{
struct config_bool *conf = (struct config_bool *) gconf;

! if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
{
! conf->session_val = conf->tentative_val;
! conf->gen.session_source = conf->gen.tentative_source;
}

! if (*conf->variable != conf->session_val)
{
if (conf->assign_hook)
! if (!(*conf->assign_hook) (conf->session_val,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
! *conf->variable = conf->session_val;
changed = true;
}
! conf->gen.source = conf->gen.session_source;
! conf->gen.status = 0;
break;
}
case PGC_INT:
{
struct config_int *conf = (struct config_int *) gconf;

! if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
{
! conf->session_val = conf->tentative_val;
! conf->gen.session_source = conf->gen.tentative_source;
}

! if (*conf->variable != conf->session_val)
{
if (conf->assign_hook)
! if (!(*conf->assign_hook) (conf->session_val,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
! *conf->variable = conf->session_val;
changed = true;
}
! conf->gen.source = conf->gen.session_source;
! conf->gen.status = 0;
break;
}
case PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;

! if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
{
! conf->session_val = conf->tentative_val;
! conf->gen.session_source = conf->gen.tentative_source;
}

! if (*conf->variable != conf->session_val)
{
if (conf->assign_hook)
! if (!(*conf->assign_hook) (conf->session_val,
true, PGC_S_OVERRIDE))
elog(LOG, "failed to commit %s",
conf->gen.name);
! *conf->variable = conf->session_val;
changed = true;
}
! conf->gen.source = conf->gen.session_source;
! conf->gen.status = 0;
break;
}
case PGC_STRING:
{
struct config_string *conf = (struct config_string *) gconf;

! if (isCommit && (conf->gen.status & GUC_HAVE_TENTATIVE))
{
! SET_STRING_SESSION_VAL(conf, conf->tentative_val);
! conf->gen.session_source = conf->gen.tentative_source;
! conf->tentative_val = NULL; /* transfer ownership */
}
else
- SET_STRING_TENTATIVE_VAL(conf, NULL);
-
- if (*conf->variable != conf->session_val)
{
! char *str = conf->session_val;

if (conf->assign_hook)
{
const char *newstr;

! newstr = (*conf->assign_hook) (str, true,
PGC_S_OVERRIDE);
if (newstr == NULL)
elog(LOG, "failed to commit %s",
conf->gen.name);
! else if (newstr != str)
{
/*
* See notes in set_config_option about
* casting
*/
! str = (char *) newstr;
! SET_STRING_SESSION_VAL(conf, str);
}
}

! SET_STRING_VARIABLE(conf, str);
changed = true;
}
! conf->gen.source = conf->gen.session_source;
! conf->gen.status = 0;
break;
}
}

if (changed && (gconf->flags & GUC_REPORT))
ReportGUCOption(gconf);
}

! guc_dirty = false;
}


--- 2525,2714 ----
case PGC_BOOL:
{
struct config_bool *conf = (struct config_bool *) gconf;
+ bool newval;
+ GucSource newsource;

! 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;
}

! 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 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;
}

! 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 PGC_REAL:
{
struct config_real *conf = (struct config_real *) gconf;
+ double newval;
+ GucSource newsource;

! if (useTentative)
{
! newval = conf->tentative_val;
! newsource = conf->gen.tentative_source;
! conf->gen.status |= GUC_HAVE_TENTATIVE;
! }
! else
! {
! newval = stack->value.realval;
! newsource = stack->source;
! conf->tentative_val = stack->tentative_val.realval;
! conf->gen.tentative_source = stack->tentative_source;
}

! 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 PGC_STRING:
{
struct config_string *conf = (struct config_string *) gconf;
+ char *newval;
+ GucSource newsource;

! if (useTentative)
{
! newval = conf->tentative_val;
! newsource = conf->gen.tentative_source;
! conf->gen.status |= GUC_HAVE_TENTATIVE;
}
else
{
! 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;
! }

+ if (*conf->variable != newval)
+ {
if (conf->assign_hook)
{
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;
}
! 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 (!isSubXact)
! set_string_field(conf, &conf->tentative_val, NULL);
break;
}
}

+ /* Finish popping the state stack */
+ gconf->stack = stack->prev;
+ pfree(stack);
+
+ /*
+ * If we're now out of all xact levels, forget TENTATIVE status bit;
+ * there's nothing tentative about the value anymore.
+ */
+ if (!isSubXact)
+ {
+ Assert(gconf->stack == NULL);
+ gconf->status = 0;
+ }
+
+ /* Report new value if we changed it */
if (changed && (gconf->flags & GUC_REPORT))
ReportGUCOption(gconf);
}

! /*
! * 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 (!isSubXact)
! guc_dirty = false;
}


***************
*** 2810,2816 ****
}

/*
! * Should we set reset/session values? (If so, the behavior is not
* transactional.)
*/
makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && (value != NULL);
--- 3012,3018 ----
}

/*
! * Should we set reset/stacked values? (If so, the behavior is not
* transactional.)
*/
makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && (value != NULL);
***************
*** 2820,2826 ****
* However, if changeVal is false then plow ahead anyway since we are
* trying to find out if the value is potentially good, not actually
* use it. Also keep going if makeDefault is true, since we may want
! * to set the reset/session values even if we can't set the variable
* itself.
*/
if (record->source > source)
--- 3022,3028 ----
* However, if changeVal is false then plow ahead anyway since we are
* trying to find out if the value is potentially good, not actually
* use it. Also keep going if makeDefault is true, since we may want
! * to set the reset/stacked values even if we can't set the variable
* itself.
*/
if (record->source > source)
***************
*** 2901,2906 ****
--- 3103,3111 ----

if (changeVal || makeDefault)
{
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
***************
*** 2908,2922 ****
}
if (makeDefault)
{
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! if (conf->gen.session_source <= source)
{
! conf->session_val = newval;
! conf->gen.session_source = source;
}
}
else if (isLocal)
--- 3113,3132 ----
}
if (makeDefault)
{
+ GucStack *stack;
+
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! for (stack = conf->gen.stack; stack; stack = stack->prev)
{
! if (stack->source <= source)
! {
! stack->value.boolval = newval;
! stack->source = source;
! }
}
}
else if (isLocal)
***************
*** 3006,3011 ****
--- 3216,3224 ----

if (changeVal || makeDefault)
{
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
***************
*** 3013,3027 ****
}
if (makeDefault)
{
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! if (conf->gen.session_source <= source)
{
! conf->session_val = newval;
! conf->gen.session_source = source;
}
}
else if (isLocal)
--- 3226,3245 ----
}
if (makeDefault)
{
+ GucStack *stack;
+
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! for (stack = conf->gen.stack; stack; stack = stack->prev)
{
! if (stack->source <= source)
! {
! stack->value.intval = newval;
! stack->source = source;
! }
}
}
else if (isLocal)
***************
*** 3101,3106 ****
--- 3319,3327 ----

if (changeVal || makeDefault)
{
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen);
if (changeVal)
{
*conf->variable = newval;
***************
*** 3108,3122 ****
}
if (makeDefault)
{
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! if (conf->gen.session_source <= source)
{
! conf->session_val = newval;
! conf->gen.session_source = source;
}
}
else if (isLocal)
--- 3329,3348 ----
}
if (makeDefault)
{
+ GucStack *stack;
+
if (conf->gen.reset_source <= source)
{
conf->reset_val = newval;
conf->gen.reset_source = source;
}
! for (stack = conf->gen.stack; stack; stack = stack->prev)
{
! if (stack->source <= source)
! {
! stack->value.realval = newval;
! stack->source = source;
! }
}
}
else if (isLocal)
***************
*** 3261,3287 ****

if (changeVal || makeDefault)
{
if (changeVal)
{
! SET_STRING_VARIABLE(conf, newval);
conf->gen.source = source;
}
if (makeDefault)
{
if (conf->gen.reset_source <= source)
{
! SET_STRING_RESET_VAL(conf, newval);
conf->gen.reset_source = source;
}
! if (conf->gen.session_source <= source)
{
! SET_STRING_SESSION_VAL(conf, newval);
! conf->gen.session_source = source;
}
/* Perhaps we didn't install newval anywhere */
! if (newval != *conf->variable &&
! newval != conf->session_val &&
! newval != conf->reset_val)
free(newval);
}
else if (isLocal)
--- 3487,3520 ----

if (changeVal || makeDefault)
{
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen);
if (changeVal)
{
! set_string_field(conf, conf->variable, newval);
conf->gen.source = source;
}
if (makeDefault)
{
+ GucStack *stack;
+
if (conf->gen.reset_source <= source)
{
! set_string_field(conf, &conf->reset_val, newval);
conf->gen.reset_source = source;
}
! for (stack = conf->gen.stack; stack; stack = stack->prev)
{
! if (stack->source <= source)
! {
! set_string_field(conf, &stack->value.stringval,
! newval);
! stack->source = source;
! }
}
/* Perhaps we didn't install newval anywhere */
! if (!string_field_used(conf, newval))
free(newval);
}
else if (isLocal)
***************
*** 3291,3297 ****
}
else
{
! SET_STRING_TENTATIVE_VAL(conf, newval);
conf->gen.tentative_source = source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
guc_dirty = true;
--- 3524,3530 ----
}
else
{
! set_string_field(conf, &conf->tentative_val, newval);
conf->gen.tentative_source = source;
conf->gen.status |= GUC_HAVE_TENTATIVE;
guc_dirty = true;
***************
*** 3608,3651 ****
/* This better be a placeholder
*/
if(((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
- {
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("attempt to redefine parameter \"%s\"", name)));
! }
! pHolder = (struct config_string*)*res;

! /* We have the same name, no sorting is necessary.
! */
*res = variable;

value = *pHolder->variable;

! /* Assign the variable stored in the placeholder to the real
! * variable.
*/
set_config_option(name, value,
pHolder->gen.context, pHolder->gen.source,
false, true);

! /* Free up stuff occupied by the placeholder variable
*/
! if(value != NULL)
! free((void*)value);
!
! if(pHolder->reset_val != NULL && pHolder->reset_val != value)
! free(pHolder->reset_val);
!
! if(pHolder->session_val != NULL
! && pHolder->session_val != value
! && pHolder->session_val != pHolder->reset_val)
! free(pHolder->session_val);
!
! if(pHolder->tentative_val != NULL
! && pHolder->tentative_val != value
! && pHolder->tentative_val != pHolder->reset_val
! && pHolder->tentative_val != pHolder->session_val)
! free(pHolder->tentative_val);

free(pHolder);
}
--- 3841,3876 ----
/* This better be a placeholder
*/
if(((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("attempt to redefine parameter \"%s\"", name)));
!
! Assert((*res)->vartype == PGC_STRING);
! pHolder = (struct config_string*) *res;

! /* We have the same name, no sorting is necessary */
*res = variable;

value = *pHolder->variable;

! /*
! * Assign the string value stored in the placeholder to the real variable.
! *
! * XXX this is not really good enough --- it should be a nontransactional
! * assignment, since we don't want it to roll back if the current xact
! * fails later.
*/
set_config_option(name, value,
pHolder->gen.context, pHolder->gen.source,
false, true);

! /*
! * Free up as much as we conveniently can of the placeholder structure
! * (this neglects any stack items...)
*/
! 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);
}
***************
*** 3754,3760 ****
define_custom_variable(&var->gen);
}

! extern void EmittWarningsOnPlaceholders(const char* className)
{
struct config_generic** vars = guc_variables;
struct config_generic** last = vars + num_guc_variables;
--- 3979,3985 ----
define_custom_variable(&var->gen);
}

! extern void EmitWarningsOnPlaceholders(const char* className)
{
struct config_generic** vars = guc_variables;
struct config_generic** last = vars + num_guc_variables;
***************
*** 5133,5137 ****
--- 5358,5371 ----
return true;
}

+ static bool
+ assign_transaction_read_only(bool newval, bool doit, GucSource source)
+ {
+ if (doit && source >= PGC_S_INTERACTIVE && IsSubTransaction())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set transaction read only mode inside a subtransaction")));
+ return true;
+ }

#include "guc-file.c"
*** src/include/utils/guc.h.orig Fri May 28 01:13:32 2004
--- src/include/utils/guc.h Tue Jun 29 13:56:33 2004
***************
*** 175,188 ****
GucStringAssignHook assign_hook,
GucShowHook show_hook);

! extern void EmittWarningsOnPlaceholders(const char* className);

extern const char *GetConfigOption(const char *name);
extern const char *GetConfigOptionResetString(const char *name);
extern void ProcessConfigFile(GucContext context);
extern void InitializeGUCOptions(void);
extern void ResetAllOptions(void);
! extern void AtEOXact_GUC(bool isCommit);
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,
--- 175,188 ----
GucStringAssignHook assign_hook,
GucShowHook show_hook);

! extern void EmitWarningsOnPlaceholders(const char* className);

extern const char *GetConfigOption(const char *name);
extern const char *GetConfigOptionResetString(const char *name);
extern void ProcessConfigFile(GucContext context);
extern void InitializeGUCOptions(void);
extern void ResetAllOptions(void);
! extern void AtEOXact_GUC(bool isCommit, bool isSubXact);
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,
*** src/include/utils/guc_tables.h.orig Wed May 26 12:50:34 2004
--- src/include/utils/guc_tables.h Tue Jun 29 13:56:33 2004
***************
*** 11,18 ****
*
*-------------------------------------------------------------------------
*/
! #ifndef GUC_TABLES
! #define GUC_TABLES 1

/*
* Groupings to help organize all the run-time options for display
--- 11,37 ----
*
*-------------------------------------------------------------------------
*/
! #ifndef GUC_TABLES_H
! #define GUC_TABLES_H 1
!
! /*
! * GUC supports these types of variables:
! */
! enum config_type
! {
! PGC_BOOL,
! PGC_INT,
! PGC_REAL,
! PGC_STRING
! };
!
! union config_var_value
! {
! bool boolval;
! int intval;
! double realval;
! char *stringval;
! };

/*
* Groupings to help organize all the run-time options for display
***************
*** 56,70 ****
};

/*
! * GUC supports these types of variables:
*/
! enum config_type
{
! PGC_BOOL,
! PGC_INT,
! PGC_REAL,
! PGC_STRING
! };

/*
* Generic fields applicable to all types of variables
--- 75,93 ----
};

/*
! * Stack entry for saving the state of a variable prior to the current
! * transaction
*/
! 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 */
! } GucStack;

/*
* Generic fields applicable to all types of variables
***************
*** 86,94 ****
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 session_source; /* source of the session_value */
GucSource tentative_source; /* source of the tentative_value */
GucSource source; /* source of the current actual value */
};

/* bit values in flags field */
--- 109,117 ----
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 */
};

/* bit values in flags field */
***************
*** 104,109 ****
--- 127,133 ----
/* 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) */


/* GUC records for specific variable types */
***************
*** 118,124 ****
GucBoolAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
- bool session_val;
bool tentative_val;
};

--- 142,147 ----
***************
*** 134,140 ****
GucIntAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
- int session_val;
int tentative_val;
};

--- 157,162 ----
***************
*** 150,156 ****
GucRealAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
- double session_val;
double tentative_val;
};

--- 172,177 ----
***************
*** 165,171 ****
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
char *reset_val;
- char *session_val;
char *tentative_val;
};

--- 186,191 ----
***************
*** 180,183 ****

extern void build_guc_variables(void);

! #endif
--- 200,203 ----

extern void build_guc_variables(void);

! #endif /* GUC_TABLES_H */

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Andrew Dunstan 2004-06-29 19:31:22 bounce messages
Previous Message Cason, Kenny 2004-06-29 19:07:30 Accessing Specific Schemas

Browse pgsql-patches by date

  From Date Subject
Next Message Simon Riggs 2004-06-29 19:59:05 Re: PITR Archive Recovery
Previous Message Andrew Dunstan 2004-06-29 17:05:40 Re: plperl patch