nested xacts and phantom Xids

Started by Alvaro Herreraover 21 years ago39 messages
#1Alvaro Herrera
alvherre@dcc.uchile.cl
2 attachment(s)

Ok people,

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

Please review, test and comment.

Missing: rollback of SET CONSTRAINTS and GUC vars.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
Y una voz del caos me habl� y me dijo
"Sonr�e y s� feliz, podr�a ser peor".
Y sonre�. Y fui feliz.
Y fue peor.

Attachments:

nested-all-14.patch.gzapplication/octet-streamDownload
phantom-xids-2.patch.gzapplication/octet-streamDownload
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#1)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I looked at the phantom XIDs stuff a bit. I still have little confidence
that the concept is correct :-( but here are some comments on the code
level.

+  * They are marked immediately in pg_subtrans as direct childs of the topmost
+  * Xid (no matter how deep we are in the transaction tree),

Why is that a good idea --- won't it cause problems when you
commit/abort an intermediate level of subtransaction?

+  * All this happens when HeapTupleHeaderSetXmax is called and the Xmin of the
+  * tuple is one of the Xids of the current transaction.
+  *
+  * Note that HeapTupleHeaderGetXmax/GetXmin return the masqueraded Xmin and
+  * Xmax, not the real one in the tuple header.

I think that putting this sort of logic into Set/Get Xmin/Xmax is a
horrid idea. They are supposed to be transparent fetch/store macros,
not arbitrarily-complex filter functions. They're also supposed to
be reasonably fast :-(

+  * ... We know to do this when SetXmax is called
+  * on a tuple that has the HEAP_MARKED_FOR_UPDATE bit set.

Uglier and uglier.

***************
*** 267,274 ****
return true;

/* deleting subtransaction aborted */
! if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
! return true;

Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));

--- 268,276 ----
return true;

/* deleting subtransaction aborted */
! if (tuple->t_infomask & HEAP_XMIN_IS_PHANTOM)
! if (TransactionPhantomIsCommittedPhantom(HeapTupleHeaderGetPhantomId(tuple)))
! return true;

Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));

Er, what happened to checking for the "deleting subtransaction aborted"
case?

On the whole, I think this was an interesting exercise but also an utter
failure. We'd be far better off to eat the extra 4 bytes per tuple
header and put back the separate Xmax field. The costs in both runtime
and complexity/reliability of this patch are simply not justified by
a 4-byte savings.

regards, tom lane

#3Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#2)
Re: nested xacts and phantom Xids

Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I looked at the phantom XIDs stuff a bit. I still have little confidence
that the concept is correct :-( but here are some comments on the code
level.

+  * They are marked immediately in pg_subtrans as direct childs of the topmost
+  * Xid (no matter how deep we are in the transaction tree),

Why is that a good idea --- won't it cause problems when you
commit/abort an intermediate level of subtransaction?

I don't think so. The phantom xid is used only by the outside
transaction waiting for that tuple to be committe or aborted. The
outside tranaction will sleep on the topmost xid completing, then check
the phantom xid status for commit/abort. Within the transaction, I think
he uses command counter to know the creation and destruction sub-xid.

I think right now phantom xids are used only for making parts of a
subtransaction committed or aborted and only in cases where the tuple is
created and destroyed by parts of the same transaction tree.

I don't feel too bad about the runtime cost if only subtransactions are
paying that cost. I know we are really stretching the system here but I
would like to try a little more rather than give up and taking a space
hit for all tuples.

---------------------------------------------------------------------------

+  * All this happens when HeapTupleHeaderSetXmax is called and the Xmin of the
+  * tuple is one of the Xids of the current transaction.
+  *
+  * Note that HeapTupleHeaderGetXmax/GetXmin return the masqueraded Xmin and
+  * Xmax, not the real one in the tuple header.

I think that putting this sort of logic into Set/Get Xmin/Xmax is a
horrid idea. They are supposed to be transparent fetch/store macros,
not arbitrarily-complex filter functions. They're also supposed to
be reasonably fast :-(

+  * ... We know to do this when SetXmax is called
+  * on a tuple that has the HEAP_MARKED_FOR_UPDATE bit set.

Uglier and uglier.

***************
*** 267,274 ****
return true;

/* deleting subtransaction aborted */
! if (TransactionIdDidAbort(HeapTupleHeaderGetXmax(tuple)))
! return true;

Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));

--- 268,276 ----
return true;

/* deleting subtransaction aborted */
! if (tuple->t_infomask & HEAP_XMIN_IS_PHANTOM)
! if (TransactionPhantomIsCommittedPhantom(HeapTupleHeaderGetPhantomId(tuple)))
! return true;

Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)));

Er, what happened to checking for the "deleting subtransaction aborted"
case?

On the whole, I think this was an interesting exercise but also an utter
failure. We'd be far better off to eat the extra 4 bytes per tuple
header and put back the separate Xmax field. The costs in both runtime
and complexity/reliability of this patch are simply not justified by
a 4-byte savings.

regards, tom lane

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://archives.postgresql.org

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#4Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Bruce Momjian (#3)
Re: nested xacts and phantom Xids

Bruce Momjian wrote:

Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I looked at the phantom XIDs stuff a bit. I still have little confidence
that the concept is correct :-( but here are some comments on the code
level.

+  * They are marked immediately in pg_subtrans as direct childs of the topmost
+  * Xid (no matter how deep we are in the transaction tree),

Why is that a good idea --- won't it cause problems when you
commit/abort an intermediate level of subtransaction?

I don't think so. The phantom xid is used only by the outside
transaction waiting for that tuple to be committe or aborted. The
outside tranaction will sleep on the topmost xid completing, then check
the phantom xid status for commit/abort. Within the transaction, I think
he uses command counter to know the creation and destruction sub-xid.

I think right now phantom xids are used only for making parts of a
subtransaction committed or aborted and only in cases where the tuple is
created and destroyed by parts of the same transaction tree.

I don't feel too bad about the runtime cost if only subtransactions are
paying that cost. I know we are really stretching the system here but I
would like to try a little more rather than give up and taking a space
hit for all tuples.

Let me add that outside transactions read those phantom xid only when
they are doing dirty reads. What I don't understand is when do outside
transactions see tuples created inside a transaction? INSERT into a
table with a unique key?

Once the main transaction commits, these phantom tuples should work just
like ordinary tuples except they get their cmin overwritten when they
are expired, I think.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#3)
Re: nested xacts and phantom Xids

Bruce Momjian <pgman@candle.pha.pa.us> writes:

I don't feel too bad about the runtime cost if only subtransactions are
paying that cost.

That's exactly why I'm so exercised about what's been done to the
HeapTupleSet/Get macros. That's significant cost that's paid even when
you're not using *any* of this stuff.

I know we are really stretching the system here but I
would like to try a little more rather than give up and taking a space
hit for all tuples.

I don't even have any confidence that there are no fundamental bugs
in the phantom-xid concept :-(. I'd be willing to play along if an
implementation that seemed acceptable speedwise were being offered,
but this thing is not preferable to four-more-bytes even if it works.

regards, tom lane

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#4)
Re: nested xacts and phantom Xids

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Let me add that outside transactions read those phantom xid only when
they are doing dirty reads. What I don't understand is when do outside
transactions see tuples created inside a transaction? INSERT into a
table with a unique key?

Once the main transaction commits, these phantom tuples should work just
like ordinary tuples except they get their cmin overwritten when they
are expired, I think.

You're not doing anything to increase my confidence level in this
concept. You invented it, and even you aren't sure how it works.

regards, tom lane

#7Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#6)
Re: nested xacts and phantom Xids

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Let me add that outside transactions read those phantom xid only when
they are doing dirty reads. What I don't understand is when do outside
transactions see tuples created inside a transaction? INSERT into a
table with a unique key?

Once the main transaction commits, these phantom tuples should work just
like ordinary tuples except they get their cmin overwritten when they
are expired, I think.

You're not doing anything to increase my confidence level in this
concept. You invented it, and even you aren't sure how it works.

And your point is? I am trying to help Alvaro. I came up with an
idea, and some thought it would help solve the problem. What ideas do
you have to help him?

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#1)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I took a very preliminary look through the nested-xacts patch, too.

Missing: rollback of SET CONSTRAINTS and GUC vars.

There's a good deal more than that missing :-(. Here are the modules or
actions that are called in CommitTransaction and/or AbortTransaction
that have not yet been touched by the patch:

shared-inval message queue processing (inval.c)
lo_commit
localbuf.c (refcounts need fixed same as bufmgr)
eoxactcallbacks API needs extension
GUC
namespace (temp namespace cleanup)
files (close/drop temp files)
reset current userid during xact abort
password file updating
SetReindexProcessing disable (can these be nested??)

Of these the one that I think most urgently needs attention is inval.c;
that is complicated code already and figuring out what should happen
for subtrans commit/abort is not trivial. The others look relatively
straightforward to fix, if tedious.

Given patches for inval.c and guc.c, I would say that the patch is
functionally close enough to done that we could commit to including
it in 7.5 --- the other stuff could be wrapped up post-feature-freeze.

BUT (you knew there was a but coming, right?) ... I am really really
disturbed about the potential performance hit from this patch.
I don't think we want to have a nested-transaction feature that imposes
significant overhead on people who aren't actually using nested
transactions. As I looked through the patch I found quite a few places
that would impose such overhead.

The main thing that's bothering me is that TransactionIdIsInProgress
has been turned from a relatively cheap test (just scan the in-memory
PGPROC array) into an exceedingly expensive one. It's gratutitously
badly coded (for N open main transactions it does N searches of the
subtrans tree, when one would do), but I don't really want it going to
the subtrans tree at all during typical cases. We had talked about
keeping a limited number of active subtrans IDs in the PGPROC array,
and I think we're really going to need to do that to buy back
performance here.

(BTW, what is the intended behavior of TransactionIdIsInProgress for
a committed or aborted subtransaction whose parent is still open?
If it has to recognize aborted subtranses as still in progress then
things get very messy, but I think it probably doesn't have to.)

I'm quite unhappy with the wide use of SubTransXidsHaveCommonAncestor to
replace TransactionIdEquals in tqual.c, too. This turns very cheap tests
into very expensive ones --- whether or not you use subtransactions ---
and unfortunately tqual.c is coded on the assumption that these tests
are cheap. We can *not* afford to repeat SubTransXidsHaveCommonAncestor
for every open main transaction every time we check a tuple. In many
ways this is the same problem as with TransactionIdIsInProgress, and
it probably needs a similar solution.

Also I found it annoying that XactLockTableWait waits for a top xact
even when target subxact has already aborted; possibly this is even a
recipe for deadlock. Perhaps should iterate up the tree one level at a
time, waiting for each? Would mean that subxacts have to acquire a
lock on their xid that other people can wait for, but this is not
necessarily bad. (In fact, if we do that then I don't think
XactLockTableWait has to change at all.)

So I think there are several must-fix performance issues as well before
we can make a decision to accept this for 7.5.

I'd feel better about this if we had another month, but with ten days
before feature freeze it's gonna be touch and go.

regards, tom lane

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#7)
Re: nested xacts and phantom Xids

Bruce Momjian <pgman@candle.pha.pa.us> writes:

What ideas do you have to help him?

Eat the four-byte overhead.

Quite frankly, given the other functionality and performance issues
he has to solve in the next ten days (see my other message just now),
I think he'd be a fool to spend one more minute on phantom XIDs.
Maybe for 7.6 someone will have the time to make the idea work.

regards, tom lane

#10Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#9)
Re: nested xacts and phantom Xids

Tom Lane wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

What ideas do you have to help him?

Eat the four-byte overhead.

Quite frankly, given the other functionality and performance issues
he has to solve in the next ten days (see my other message just now),
I think he'd be a fool to spend one more minute on phantom XIDs.
Maybe for 7.6 someone will have the time to make the idea work.

Well, the problem with eating the 4-byte overhead is that it has a
performance/storage impact for all installations, not just those that
use nested transactions. You were discussing a performance impact for
nested transactions, but I assume that is an impact when some on the
server are using nested transactions and others are not, while this hit
would be for all sites, even when no one is using nested transactions.

However, given the list you just supplied, I agree Alvaro should focus
on these items and add the 4-bytes to store needed values rather than
continue to hack on phantom xids. We can certainly revisit that later,
even in 7.6, or never. We can put Manfred on it. He did the compaction
in the first place. :-)

One point is that if we want to re-add those 4 bytes, we have to get
community buy-in for it because it is a step backward for those who are
not going to use nested transactions. (I don't want to go the
compile-time switch route.)

As far as cleaning up some of the items after feature freeze, well, I
would like to avoid that, but it seems safe to do because the changes
should be very localized, and because win32 will be cleaning up things
during feature freeze too, I am sure.

However, going into feature freeze knowing a features isn't 100%
functional is something we try to avoid, so this would have to be a
special exception based on the fact that the features is very difficult,
and that Alvaro is working full-time on this. It does take us down the
slippery slope of having the release process dictated by completion of
nested transactions.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#11Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#2)
Re: nested xacts and phantom Xids

On Sun, Jun 20, 2004 at 04:37:16PM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I looked at the phantom XIDs stuff a bit. I still have little confidence
that the concept is correct :-( but here are some comments on the code
level.

Ok. I for one think this got much more complex than I had originally
thought it would be. I agree the changes to Set/Get Xmin/Xmax are way
beyond what one would want, but the alternative would be to spread the
complexity into their callers and I think that would be much worse.

I don't have a lot of confidence in this either. The patch will be
available in archives if anybody wants to implement this in a cleaner
and safer way; I'll continue working on the rest of the things you
pointed out in the subtransactions patch.

Sadly, something broke in a really strange way between my last cvs
update and today's, because I can't get the patch to initdb. It
compiles without warnings (and I did make distclean), but there's a
weird bug I'll have to pursue.

Regarding the invalidation messages, what I'm currently looking at is to
add a TransactionId to each message, which will be CurrentTransactionId
for each new message. When a subxact commits, all its messages are
relabeled to its parent. When a subxact aborts, all messages labeled
with its TransactionId are locally processed and discarded. This is
tricky because chunks are prepended to the queue, but it also means we
can stop processing as soon as a message belongs to another transaction.

I think GUC vars are easier: when a variable is about to change value
inside a subtransaction, save the previous value in a list from which it
will be restored if the subtransaction later aborts.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"Hay dos momentos en la vida de un hombre en los que no deber�a
especular: cuando puede permit�rselo y cuando no puede" (Mark Twain)

#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#11)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Regarding the invalidation messages, what I'm currently looking at is to
add a TransactionId to each message, which will be CurrentTransactionId
for each new message. When a subxact commits, all its messages are
relabeled to its parent. When a subxact aborts, all messages labeled
with its TransactionId are locally processed and discarded.

Sounds plausible offhand.

This is tricky because chunks are prepended to the queue, but it also
means we can stop processing as soon as a message belongs to another
transaction.

AFAIR there isn't any essential semantics to the ordering of the queue
entries, so you can probably get away with reordering if that makes life
any easier.

Also, rather than labeling each entry individually, it might be better
to keep a separate list for each level of transaction. Then instead of
relabeling, you'd just concat the subtrans list to its parent's. Seems
like this should be faster and less storage.

regards, tom lane

#13Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#12)
Re: nested xacts and phantom Xids

On Mon, Jun 21, 2004 at 10:28:59PM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

This is tricky because chunks are prepended to the queue, but it also
means we can stop processing as soon as a message belongs to another
transaction.

AFAIR there isn't any essential semantics to the ordering of the queue
entries, so you can probably get away with reordering if that makes life
any easier.

Also, rather than labeling each entry individually, it might be better
to keep a separate list for each level of transaction. Then instead of
relabeling, you'd just concat the subtrans list to its parent's. Seems
like this should be faster and less storage.

Right, but it makes harder to detect when a duplicate relcache entry is
going to be inserted. Judging from the commentary in the file this is
an issue.

Another idea I had was:
1. at subtransaction start, add a special "boundary" message carrying
the new Xid.
2. at subtransaction abort, process the first chunk backwards until the
boundary message with the corresponding Xid is found; if it's not, jump
to the next and repeat.

At subtransaction commit nothing special needs to be done.

This avoids having to label each message and to change the message
appending scheme.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"The eagle never lost so much time, as
when he submitted to learn of the crow." (William Blake)

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#13)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

On Mon, Jun 21, 2004 at 10:28:59PM -0400, Tom Lane wrote:

Also, rather than labeling each entry individually, it might be better
to keep a separate list for each level of transaction. Then instead of
relabeling, you'd just concat the subtrans list to its parent's. Seems
like this should be faster and less storage.

Right, but it makes harder to detect when a duplicate relcache entry is
going to be inserted. Judging from the commentary in the file this is
an issue.

It's only a minor efficiency hack; don't get too tense about avoiding dups.
(We don't bother to check for dup catcache-inval entries at all...)
The only thing I'd suggest you need to preserve is the business about
invalidating catcache before relcache; again that's just an efficiency
hack, but it seems simple enough to be worth doing.

regards, tom lane

#15Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#8)
1 attachment(s)
Re: nested xacts and phantom Xids

On Sun, Jun 20, 2004 at 08:49:22PM -0400, Tom Lane wrote:

Of these the one that I think most urgently needs attention is inval.c;
that is complicated code already and figuring out what should happen
for subtrans commit/abort is not trivial. The others look relatively
straightforward to fix, if tedious.

A patch for this is attached. It _seems_ to work ok; conceptually it
seems ok too. I have done some testing which tends to indicate that it
is right, but I'm not sure of all the implications this code has so I
don't know how to test it exhaustively. Of course, regression tests
pass, but this doesn't actually prove anything.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"�Que diferencia tiene para los muertos, los hu�rfanos, y aquellos que han
perdido su hogar, si la loca destrucci�n ha sido realizada bajo el nombre
del totalitarismo o del santo nombre de la libertad y la democracia?" (Gandhi)

Attachments:

inval.patchtext/plain; charset=us-asciiDownload
Index: src/include/storage/sinval.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/include/storage/sinval.h,v
retrieving revision 1.35
diff -c -w -b -B -c -r1.35 sinval.h
*** sinval.h	2 Jun 2004 21:29:29 -0000	1.35
--- sinval.h	22 Jun 2004 04:52:54 -0000
***************
*** 20,32 ****
  
  
  /*
!  * We currently support two types of shared-invalidation messages: one that
   * invalidates an entry in a catcache, and one that invalidates a relcache
   * entry.  More types could be added if needed.  The message type is
   * identified by the first "int16" field of the message struct.  Zero or
   * positive means a catcache inval message (and also serves as the catcache
!  * ID field).  -1 means a relcache inval message.  Other negative values
!  * are available to identify other inval message types.
   *
   * Relcache invalidation messages usually also cause invalidation of entries
   * in the smgr's relation cache.  This means they must carry both logical
--- 20,33 ----
  
  
  /*
!  * We currently support three types of shared-invalidation messages: one that
   * invalidates an entry in a catcache, and one that invalidates a relcache
   * entry.  More types could be added if needed.  The message type is
   * identified by the first "int16" field of the message struct.  Zero or
   * positive means a catcache inval message (and also serves as the catcache
!  * ID field).  -1 means a relcache inval message.  -2 means a subtransaction
!  * boundary message. Other negative values are available to identify other
!  * inval message types.
   *
   * Relcache invalidation messages usually also cause invalidation of entries
   * in the smgr's relation cache.  This means they must carry both logical
***************
*** 53,58 ****
--- 54,64 ----
   * and so that negative cache entries can be recognized with good accuracy.
   * (Of course this assumes that all the backends are using identical hashing
   * code, but that should be OK.)
+  *
+  * A subtransaction boundary is not really a cache invalidation message;
+  * rather it's an implementation artifact for nested transactions.  The
+  * cleanup code for subtransaction abort looks for this message as a boundary
+  * to know when to stop processing messages.
   */
  
  typedef struct
***************
*** 79,89 ****
--- 85,104 ----
  	 */
  } SharedInvalRelcacheMsg;
  
+ #define SUBXACTBOUNDARYMSG_ID (-2)
+ 
+ typedef struct
+ {
+ 	int16		id;				/* type field --- must be first */
+ 	TransactionId	xid;		/* transaction id */
+ } SubxactBoundaryMsg;
+ 
  typedef union
  {
  	int16		id;				/* type field --- must be first */
  	SharedInvalCatcacheMsg cc;
  	SharedInvalRelcacheMsg rc;
+ 	SubxactBoundaryMsg	   sb;
  } SharedInvalidationMessage;
  
  
Index: src/backend/utils/cache/inval.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/cache/inval.c,v
retrieving revision 1.62
diff -c -w -b -B -c -r1.62 inval.c
*** inval.c	18 Jun 2004 06:13:52 -0000	1.62
--- inval.c	23 Jun 2004 00:04:35 -0000
***************
*** 66,73 ****
   *	manipulating the init file is in relcache.c, but we keep track of the
   *	need for it here.
   *
!  *	All the request lists are kept in TopTransactionContext memory, since
   *	they need not live beyond the end of the current transaction.
   *
   *
   * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
--- 66,75 ----
   *	manipulating the init file is in relcache.c, but we keep track of the
   *	need for it here.
   *
!  *	All the request lists are kept in CommitContext memory, since
   *	they need not live beyond the end of the current transaction.
+  *	Also, this makes it easy to free messages created in an aborting
+  *	subtransaction.
   *
   *
   * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
***************
*** 80,85 ****
--- 82,88 ----
   */
  #include "postgres.h"
  
+ #include "access/xact.h"
  #include "catalog/catalog.h"
  #include "miscadmin.h"
  #include "storage/sinval.h"
***************
*** 119,127 ****
   *	   other backends or rolled back from local cache when we commit
   *	   or abort the transaction.
   *
!  * The relcache-file-invalidated flag can just be a simple boolean,
!  * since we only act on it at transaction commit; we don't care which
!  * command of the transaction set it.
   *----------------
   */
  
--- 122,140 ----
   *	   other backends or rolled back from local cache when we commit
   *	   or abort the transaction.
   *
!  *	Note that the second list will carry a subtransaction boundary message
!  *	for each running subtransaction.  It will be used to determine which
!  *	messages should be dropped from the list if the subtransaction aborts.
!  *
!  * The relcache-file-invalidated flag is a TransactionId which shows
!  * what level of the transaction tree is requesting a invalidation.
!  * To register an invalidation, the transaction saves its own TransactionId
!  * in RelcacheInitFileInval, unless the value was already set to
!  * a valid TransactionId.  If it aborts and the value is its TransactionId,
!  * it resets the value to InvalidTransactionId.  If it commits, it changes
!  * the value to its parent's TransactionId.  This way the value is propagated
!  * up to the topmost transaction, which will delete the file if a valid
!  * TransactionId is detected.
   *----------------
   */
  
***************
*** 131,137 ****
  /* head of previous-commands event list */
  static InvalidationListHeader PriorCmdInvalidMsgs;
  
! static bool RelcacheInitFileInval;		/* init file must be invalidated? */
  
  /*
   * Dynamically-registered callback functions.  Current implementation
--- 144,151 ----
  /* head of previous-commands event list */
  static InvalidationListHeader PriorCmdInvalidMsgs;
  
! /* init file must be invalidated? */
! static TransactionId RelcacheInitFileInval = InvalidTransactionId;
  
  /*
   * Dynamically-registered callback functions.  Current implementation
***************
*** 176,182 ****
  		/* First time through; create initial chunk */
  #define FIRSTCHUNKSIZE 16
  		chunk = (InvalidationChunk *)
! 			MemoryContextAlloc(TopTransactionContext,
  							   sizeof(InvalidationChunk) +
  				(FIRSTCHUNKSIZE - 1) *sizeof(SharedInvalidationMessage));
  		chunk->nitems = 0;
--- 190,196 ----
  		/* First time through; create initial chunk */
  #define FIRSTCHUNKSIZE 16
  		chunk = (InvalidationChunk *)
! 			MemoryContextAlloc(CommitContext,
  							   sizeof(InvalidationChunk) +
  				(FIRSTCHUNKSIZE - 1) * sizeof(SharedInvalidationMessage));
  		chunk->nitems = 0;
***************
*** 190,196 ****
  		int			chunksize = 2 * chunk->maxitems;
  
  		chunk = (InvalidationChunk *)
! 			MemoryContextAlloc(TopTransactionContext,
  							   sizeof(InvalidationChunk) +
  					 (chunksize - 1) *sizeof(SharedInvalidationMessage));
  		chunk->nitems = 0;
--- 204,210 ----
  		int			chunksize = 2 * chunk->maxitems;
  
  		chunk = (InvalidationChunk *)
! 			MemoryContextAlloc(CommitContext,
  							   sizeof(InvalidationChunk) +
  					 (chunksize - 1) * sizeof(SharedInvalidationMessage));
  		chunk->nitems = 0;
***************
*** 208,214 ****
   *
   * NOTE: when we are about to commit or abort a transaction, it's
   * not really necessary to pfree the lists explicitly, since they will
!  * go away anyway when TopTransactionContext is destroyed.
   */
  static void
  FreeInvalidationMessageList(InvalidationChunk **listHdr)
--- 222,228 ----
   *
   * NOTE: when we are about to commit or abort a transaction, it's
   * not really necessary to pfree the lists explicitly, since they will
!  * go away anyway when CommitContext is destroyed.
   */
  static void
  FreeInvalidationMessageList(InvalidationChunk **listHdr)
***************
*** 405,412 ****
  	 * If the relation being invalidated is one of those cached in the
  	 * relcache init file, mark that we need to zap that file at commit.
  	 */
! 	if (RelationIdIsInInitFile(relId))
! 		RelcacheInitFileInval = true;
  }
  
  /*
--- 420,427 ----
  	 * If the relation being invalidated is one of those cached in the
  	 * relcache init file, mark that we need to zap that file at commit.
  	 */
! 	if (RelationIdIsInInitFile(relId) && RelcacheInitFileInval == InvalidTransactionId)
! 		RelcacheInitFileInval = GetCurrentTransactionId();
  }
  
  /*
***************
*** 467,472 ****
--- 482,489 ----
  				smgrclosenode(msg->rc.physId);
  		}
  	}
+ 	else if (msg->id == SUBXACTBOUNDARYMSG_ID)
+ 		;	/* do nothing */
  	else
  		elog(FATAL, "unrecognized SI message id: %d", msg->id);
  }
***************
*** 619,624 ****
--- 636,662 ----
  }
  
  /*
+  * AtSubXactStart_Inval
+  * 		Insert subtransaction boundary messages to the lists.
+  * 	
+  * 	Note that we cheat and append the message to the prior cmd list.  We can do
+  * 	this because we know it will be in the same relative position (last
+  * 	of prior cmds, first of current cmd), and it allows us to save searching the
+  * 	current cmd's list for the message in case of subtransaction abort.
+  */
+ void
+ AtSubStart_Inval(void)
+ {
+ 	SharedInvalidationMessage msg;
+ 
+ 	msg.sb.id = SUBXACTBOUNDARYMSG_ID;
+ 	msg.sb.xid = GetCurrentTransactionId();
+ 
+ 	AddInvalidationMessage(&(PriorCmdInvalidMsgs.cclist), &msg);
+ 	AddInvalidationMessage(&(PriorCmdInvalidMsgs.rclist), &msg);
+ }
+ 
+ /*
   * AtEOXactInvalidationMessages
   *		Process queued-up invalidation messages at end of transaction.
   *
***************
*** 636,642 ****
   * the caches yet.
   *
   * In any case, reset the various lists to empty.  We need not physically
!  * free memory here, since TopTransactionContext is about to be emptied
   * anyway.
   *
   * Note:
--- 674,680 ----
   * the caches yet.
   *
   * In any case, reset the various lists to empty.  We need not physically
!  * free memory here, since CommitContext is about to be emptied
   * anyway.
   *
   * Note:
***************
*** 652,658 ****
  		 * and after we send the SI messages.  However, we need not do
  		 * anything unless we committed.
  		 */
! 		if (RelcacheInitFileInval)
  			RelationCacheInitFileInvalidate(true);
  
  		AppendInvalidationMessages(&PriorCmdInvalidMsgs,
--- 690,696 ----
  		 * and after we send the SI messages.  However, we need not do
  		 * anything unless we committed.
  		 */
! 		if (RelcacheInitFileInval != InvalidTransactionId)
  			RelationCacheInitFileInvalidate(true);
  
  		AppendInvalidationMessages(&PriorCmdInvalidMsgs,
***************
*** 661,667 ****
  		ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
  									SendSharedInvalidMessage);
  
! 		if (RelcacheInitFileInval)
  			RelationCacheInitFileInvalidate(false);
  	}
  	else
--- 699,705 ----
  		ProcessInvalidationMessages(&PriorCmdInvalidMsgs,
  									SendSharedInvalidMessage);
  
! 		if (RelcacheInitFileInval != InvalidTransactionId)
  			RelationCacheInitFileInvalidate(false);
  	}
  	else
***************
*** 670,682 ****
  									LocalExecuteInvalidationMessage);
  	}
  
! 	RelcacheInitFileInval = false;
  
  	DiscardInvalidationMessages(&PriorCmdInvalidMsgs, false);
  	DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
  }
  
  /*
   * CommandEndInvalidationMessages
   *		Process queued-up invalidation messages at end of one command
   *		in a transaction.
--- 708,849 ----
  									LocalExecuteInvalidationMessage);
  	}
  
! 	RelcacheInitFileInval = InvalidTransactionId;
  
  	DiscardInvalidationMessages(&PriorCmdInvalidMsgs, false);
  	DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
  }
  
  /*
+  * Propagate the invalidate-init-file flag.
+  */
+ void
+ AtSubCommit_Inval(void)
+ {
+ 	if (RelcacheInitFileInval == GetCurrentTransactionId())
+ 		RelcacheInitFileInval = GetParentTransactionId();
+ }
+ 
+ /*
+  * AtSubAbort_Inval
+  * 		Process the invalidation list during subtransaction abort.
+  *
+  * During subtransaction abort, we have to discard the messages from
+  * the current command, and locally process the messages from prior
+  * commands that were inserted after the subtransaction boundary message.
+  */
+ void
+ AtSubAbort_Inval(void)
+ {
+ 	InvalidationChunk  *chunk,
+ 					   *savechunk;
+ 	TransactionId		myXid = GetCurrentTransactionId();
+ 
+ 	/*
+ 	 * Drop the invalidate-init-file flag, if I set it up.
+ 	 */
+ 	if (RelcacheInitFileInval == myXid)
+ 		RelcacheInitFileInval = InvalidTransactionId;
+ 
+ 	/* 
+ 	 * We can discard the current command's messages because they haven't
+ 	 * been executed.  We don't need to free the messages because the
+ 	 * CommitContext is going away soon.
+ 	 *
+ 	 * Note that we cheated in AtSubStart_Inval by inserting the boundary
+ 	 * message in the PriorCmd list.  If we hadn't done that, the message
+ 	 * could still be in CurrentCmdInvalidMsgs!!!
+ 	 */
+ 	DiscardInvalidationMessages(&CurrentCmdInvalidMsgs, false);
+ 
+ 	/*
+ 	 * Sadly we have to break the chunk abstraction here.
+ 	 * We need to walk it backwards, sort of -- starting from the
+ 	 * last item on the first chunk, to the first item of same, until
+ 	 * the subtransaction boundary message is found.  If it isn't,
+ 	 * jump to the next chunk and repeat.
+ 	 *
+ 	 * While walking, process the messages.  When the boundary message
+ 	 * is found, use its position as "last message" in the chunk, and
+ 	 * save the chunk as the head of the message list.  There's no need
+ 	 * to free the dropped chunks, because they'll go away with the
+ 	 * CommitContext.
+ 	 *
+ 	 * Start with the catcache message list.
+ 	 */
+ 	chunk = PriorCmdInvalidMsgs.cclist;
+ 	savechunk = NULL;
+ 	while (chunk != NULL)
+ 	{
+ 		int		i;
+ 
+ 		for (i = chunk->nitems - 1; i >= 0; i--)
+ 		{
+ 			if (chunk->msgs[i].id == SUBXACTBOUNDARYMSG_ID &&
+ 					((SubxactBoundaryMsg *) &(chunk->msgs[i]))->xid == myXid)
+ 			{
+ 				/*
+ 				 * Drop the already processed messages.  If this is the first
+ 				 * message in the chunk, use the next chunk as the head of
+ 				 * the list.  Else, use this chunk, but be sure to adjust the
+ 				 * number of items.
+ 				 */
+ 				if (i == 0)
+ 					savechunk = chunk->next;
+ 				else
+ 				{
+ 					chunk->nitems = i;
+ 					savechunk = chunk;
+ 				}
+ 				goto donecatcache;
+ 			}
+ 			LocalExecuteInvalidationMessage(&(chunk->msgs[i]));
+ 		}
+ 		chunk = chunk->next;
+ 		/*
+ 		 * The next chunk can't be null because this means we
+ 		 * failed to find the boundary message.
+ 		 */
+ 		Assert(chunk != NULL);
+ 	}
+ donecatcache:
+ 	PriorCmdInvalidMsgs.cclist = savechunk;
+ 
+ 	/* do the same for the Relcache message list */
+ 	chunk = PriorCmdInvalidMsgs.rclist;
+ 	while (chunk != NULL)
+ 	{
+ 		int i;
+ 
+ 		for (i = chunk->nitems - 1; i >= 0; i--)
+ 		{
+ 			if (chunk->msgs[i].id == SUBXACTBOUNDARYMSG_ID &&
+ 					((SubxactBoundaryMsg *) &(chunk->msgs[i]))->xid == myXid)
+ 			{
+ 				/* Same as above.  This code could be made a macro. */
+ 				if (i == 0)
+ 					savechunk = chunk->next;
+ 				else
+ 				{
+ 					chunk->nitems = i;
+ 					savechunk = chunk;
+ 				}
+ 				goto donerelcache;
+ 			}
+ 			LocalExecuteInvalidationMessage(&(chunk->msgs[i]));
+ 		}
+ 		chunk = chunk->next;
+ 		/*
+ 		 * The next chunk can't be null because this means we
+ 		 * failed to find the boundary message.
+ 		 */
+ 		Assert(chunk != NULL);
+ 	}
+ donerelcache:
+ 	PriorCmdInvalidMsgs.rclist = savechunk;
+ }
+ 
+ /*
   * CommandEndInvalidationMessages
   *		Process queued-up invalidation messages at end of one command
   *		in a transaction.
#16Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Alvaro Herrera (#11)
Re: nested xacts and phantom Xids

Alvaro Herrera wrote:

On Sun, Jun 20, 2004 at 04:37:16PM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Here I present the nested transactions patch and the phantom Xids patch
that goes with it.

I looked at the phantom XIDs stuff a bit. I still have little confidence
that the concept is correct :-( but here are some comments on the code
level.

Ok. I for one think this got much more complex than I had originally
thought it would be. I agree the changes to Set/Get Xmin/Xmax are way
beyond what one would want, but the alternative would be to spread the
complexity into their callers and I think that would be much worse.

I don't have a lot of confidence in this either. The patch will be
available in archives if anybody wants to implement this in a cleaner
and safer way; I'll continue working on the rest of the things you
pointed out in the subtransactions patch.

I am sorry to have given Alvaro another idea that didn't work. However,
thinking of options, I wonder if instead of phantom xids, we should do
phantom cids. Because only the local backend looks at the command
counter (cid). I think it might be alot cleaner. The phantom cid would
have a tuple bit set indicating that instead of being a cid, it is an
index into an array of cmin/cmax pairs.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#17Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Alvaro Herrera (#11)
Re: nested xacts and phantom Xids

If we add nested transactions for 7.5, are we going to need savepoints
too in the same release? If we don't, are we going to get a lot of
complaints?

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
#18Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Bruce Momjian (#17)
Re: nested xacts and phantom Xids

On Wed, Jun 23, 2004 at 08:58:15AM -0400, Bruce Momjian wrote:

If we add nested transactions for 7.5, are we going to need savepoints
too in the same release? If we don't, are we going to get a lot of
complaints?

It'd be good to have savepoints right now. I'm not sure it'd be good to
expose the nested transactions implementation if we are going to offer
savepoints later, because it means we will have to keep nested
transactions forever.

Maybe it is a good idea to hide the implementation details and offer
only the standard SAVEPOINT feature. I'm not sure how hard it is to
change the syntax; I don't think it'd be a big deal.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"La conclusi�n que podemos sacar de esos estudios es que
no podemos sacar ninguna conclusi�n de ellos" (Tanenbaum)

#19Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Bruce Momjian (#16)
Re: nested xacts and phantom Xids

On Wed, Jun 23, 2004 at 08:57:11AM -0400, Bruce Momjian wrote:

I am sorry to have given Alvaro another idea that didn't work.

It allowed me to learn a lot of cool tricks, so it wasn't wasted work.
The only think I'm sorry about is that I should have used the time for
something more useful, like tackling the remaining problems in the
nested xacts implementation proper.

However, thinking of options, I wonder if instead of phantom xids, we
should do phantom cids. Because only the local backend looks at the
command counter (cid). I think it might be alot cleaner. The phantom
cid would have a tuple bit set indicating that instead of being a cid,
it is an index into an array of cmin/cmax pairs.

Yeah, maybe this can work. I'm not going to try however, at least not
now. If somebody else wants to try, be my guest.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"El conflicto es el camino real hacia la uni�n"

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#18)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

It'd be good to have savepoints right now. I'm not sure it'd be good to
expose the nested transactions implementation if we are going to offer
savepoints later, because it means we will have to keep nested
transactions forever.

Nested transactions are *good*. We should not hide them.

I don't object to offering spec-compatible savepoints, but the plain
fact of the matter is that that's a dumbed-down API. We should not
feel constrained to offer only that.

regards, tom lane

#21Simon Riggs
simon@2ndquadrant.com
In reply to: Bruce Momjian (#16)
Re: nested xacts and phantom Xids

On Wed, 2004-06-23 at 13:57, Bruce Momjian wrote:

I am sorry to have given Alvaro another idea that didn't work.

No way! Keep having the ideas, please.

I've done some more digging in dead time on all of this and I think
we're on the right course in general by implementing all of this.

...well done to Alvaro for being able to make the ideas reality.

Best regards, Simon Riggs

#22Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#8)
1 attachment(s)
Re: nested xacts and phantom Xids

On Sun, Jun 20, 2004 at 08:49:22PM -0400, Tom Lane wrote:

Regarding GUC, a WIP report:

Given patches for inval.c and guc.c, I would say that the patch is
functionally close enough to done that we could commit to including
it in 7.5 --- the other stuff could be wrapped up post-feature-freeze.

I figured I could save the values whenever they are going to change, and
restore them if the subtransaction aborts. This seems to work fine
(lightly tested).

I still have to figure out how to handle allocation for string vars, but
I thought I'd post the patch for others to see. Please let me know if
it's too ugly. (This patch misses the pieces in xact.c and xact.h but
I'm sure the concept is clear.)

I'll post a full patch once the missing deferred trigger stuff works.
With the patches I posted to inval.c I think this fulfills the
requirements, barring the performance issues raised.

Comments?

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"No single strategy is always right (Unless the boss says so)"
(Larry Wall)

Attachments:

guc.patchtext/plain; charset=us-asciiDownload
Index: guc.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.211
diff -c -w -b -B -c -r1.211 guc.c
*** guc.c	11 Jun 2004 03:54:54 -0000	1.211
--- guc.c	24 Jun 2004 23:41:42 -0000
***************
*** 25,30 ****
--- 25,31 ----
  #include "utils/guc.h"
  #include "utils/guc_tables.h"
  
+ #include "access/xact.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
  #include "commands/async.h"
***************
*** 54,59 ****
--- 55,61 ----
  #include "tcop/tcopprot.h"
  #include "utils/array.h"
  #include "utils/builtins.h"
+ #include "utils/memutils.h"
  #include "utils/pg_locale.h"
  #include "pgstat.h"
  
***************
*** 76,81 ****
--- 78,85 ----
  static const char *assign_log_destination(const char *value,
  				bool doit, GucSource source);
  
+ static void SaveGucVariable(struct config_generic *conf);
+ 
  #ifdef HAVE_SYSLOG
  extern char *Syslog_facility;
  extern char *Syslog_ident;
***************
*** 105,110 ****
--- 109,115 ----
  						   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);
  
  
  /*
***************
*** 172,177 ****
--- 177,183 ----
  static int	max_index_keys;
  static int	max_identifier_length;
  static int	block_size;
+ static int	nesting_level;
  static bool integer_datetimes;
  
  /* Macros for freeing malloc'd pointers only if appropriate to do so */
***************
*** 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,
--- 807,813 ----
  			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,
***************
*** 1311,1316 ****
--- 1317,1333 ----
  		BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
  	},
  
+ 	{
+ 		/* XXX probably it's a bad idea for this to be GUC_REPORT. */
+ 		{"nesting_level", PGC_INTERNAL, UNGROUPED,
+ 			gettext_noop("Shows the current transaction nesting level"),
+ 			NULL,
+ 			GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_REPORT
+ 		},
+ 		&nesting_level,
+ 		0, 0, INT_MAX, NULL, NULL
+ 	},
+ 
  	/* End-of-list marker */
  	{
  		{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
***************
*** 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 */
--- 2018,2030 ----
  			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 */
***************
*** 2899,2904 ****
--- 2915,2922 ----
  						return false;
  					}
  
+ 				SaveGucVariable(record);
+ 
  				if (changeVal || makeDefault)
  				{
  					if (changeVal)
***************
*** 3004,3009 ****
--- 3022,3029 ----
  						return false;
  					}
  
+ 				SaveGucVariable(record);
+ 
  				if (changeVal || makeDefault)
  				{
  					if (changeVal)
***************
*** 3099,3104 ****
--- 3119,3126 ----
  						return false;
  					}
  
+ 				SaveGucVariable(record);
+ 
  				if (changeVal || makeDefault)
  				{
  					if (changeVal)
***************
*** 3257,3262 ****
--- 3279,3286 ----
  					}
  				}
  
+ 				SaveGucVariable(record);
+ 
  				guc_string_workspace = NULL;
  
  				if (changeVal || makeDefault)
***************
*** 4744,4749 ****
--- 4768,4909 ----
  	return newarray;
  }
  
+ typedef struct SaveGucBoolVar
+ {
+ 	bool	variable;
+ 	bool	session_val;
+ 	bool	tentative_val;
+ } SaveGucBoolVar;
+ 
+ typedef struct SaveGucIntVar
+ {
+ 	int		variable;
+ 	int		session_val;
+ 	int		tentative_val;
+ } SaveGucIntVar;
+ 
+ typedef struct SaveGucRealVar
+ {
+ 	double	variable;
+ 	double	session_val;
+ 	double	tentative_val;
+ } SaveGucRealVar;
+ 
+ typedef struct SaveGucStringVar
+ {
+ 	char	*variable;
+ 	char	*session_val;
+ 	char	*tentative_val;
+ } SaveGucStringVar;
+ 
+ typedef struct SaveGucVar
+ {
+ 	struct config_generic *record;
+ 	union {
+ 		SaveGucBoolVar		boolvar;
+ 		SaveGucIntVar		intvar;
+ 		SaveGucRealVar		realvar;
+ 		SaveGucStringVar	stringvar;
+ 	};
+ } SaveGucVar;
+ 
+ void
+ SaveGucVariable(struct config_generic *record)
+ {
+ 	MemoryContext old_cxt;
+ 	SaveGucVar *save;
+ 
+ 	/*
+ 	 * don't bother if we are not inside a subtransaction.
+ 	 */
+ 	if (!IsSubTransaction())
+ 		return;
+ 
+ 	/* 
+ 	 * no point in saving this.
+ 	 */
+ 	if (record->name == "nesting_level")
+ 		return;
+ 
+ 	old_cxt = MemoryContextSwitchTo(CommitContext);
+ 
+ 	save = (SaveGucVar *) palloc(sizeof(SaveGucVar));
+ 	save->record = record;
+ 
+ 	switch (record->vartype)
+ 	{
+ 		case PGC_BOOL:
+ 			save->boolvar.variable = *((struct config_bool *)record)->variable;
+ 			save->boolvar.session_val = ((struct config_bool *)record)->session_val;
+ 			save->boolvar.tentative_val = ((struct config_bool *)record)->tentative_val;
+ 			break;
+ 
+ 		case PGC_INT:
+ 			save->intvar.variable = *((struct config_int *)record)->variable;
+ 			save->intvar.session_val = ((struct config_int *)record)->session_val;
+ 			save->intvar.tentative_val = ((struct config_int *)record)->tentative_val;
+ 			break;
+ 
+ 		case PGC_REAL:
+ 			save->realvar.variable = *((struct config_real *)record)->variable;
+ 			save->realvar.session_val = ((struct config_real *)record)->session_val;
+ 			save->realvar.tentative_val = ((struct config_real *)record)->tentative_val;
+ 			break;
+ 
+ #ifdef NOT_USED
+ 		case PGC_STRING:
+ 			save->stringvar.variable = pstrdup(*((struct config_string *)record)->variable);
+ 			save->stringvar.session_val = ((struct config_string *)record)->session_val;
+ 			save->stringvar.tentative_val = ((struct config_string *)record)->tentative_val;
+ 			break;
+ #endif
+ 
+ 		default:
+ 			elog(DEBUG2, "ignoring unsupported GUC vartype %d", record->vartype);
+ 
+ 	}
+ 
+ 	TransactionSaveGucVar(save);
+ 	MemoryContextSwitchTo(old_cxt);
+ }
+ 
+ void
+ RestoreGucVariable(void *pointer)
+ {
+ 	SaveGucVar *save = (SaveGucVar *)pointer;
+ 
+ 	switch (save->record->vartype)
+ 	{
+ 		case PGC_BOOL:
+ 			*((struct config_bool *)save->record)->variable = save->boolvar.variable;
+ 			((struct config_bool *)save->record)->session_val = save->boolvar.session_val;
+ 			((struct config_bool *)save->record)->tentative_val = save->boolvar.tentative_val;
+ 			break;
+ 
+ 		case PGC_INT:
+ 			*((struct config_int *)save->record)->variable = save->intvar.variable;
+ 			((struct config_int *)save->record)->session_val = save->intvar.session_val;
+ 			((struct config_int *)save->record)->tentative_val = save->intvar.tentative_val;
+ 			break;
+ 
+ 		case PGC_REAL:
+ 			*((struct config_real *)save->record)->variable = save->realvar.variable;
+ 			((struct config_real *)save->record)->session_val = save->realvar.session_val;
+ 			((struct config_real *)save->record)->tentative_val = save->realvar.tentative_val;
+ 			break;
+ 
+ #ifdef NOT_USED
+ 		case PGC_STRING:
+ 			*((struct config_string *)save->record)->variable = save->stringvar.variable;
+ 			((struct config_string *)save->record)->session_val = save->stringvar.session_val;
+ 			((struct config_string *)save->record)->tentative_val = save->stringvar.tentative_val;
+ 			break;
+ #endif
+ 
+ 		default:
+ 			elog(DEBUG2, "ignoring saved value type %d", save->record->vartype);
+ 	}
+ }
  
  /*
   * assign_hook subroutines
***************
*** 5133,5137 ****
--- 5293,5306 ----
  	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"
#23Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Alvaro Herrera (#22)
1 attachment(s)
Re: nested xacts and phantom Xids

I said

I'll post a full patch once the missing deferred trigger stuff works.
With the patches I posted to inval.c I think this fulfills the
requirements, barring the performance issues raised.

Ok, here is a full patch that includes:

- all what was in the previous patch
- GUC vars are rolled back on subxact abort
- the inval msg lists are cleaned up of items created in the aborting
subxact
- SET CONSTRAINTS is rolled back too

Missing:

- all the other items Tom mentioned in his last mail, including
performance enhancements.

- discussion whether we want a different syntax for subxacts, like
SUBBEGIN/SUBCOMMIT/SUBABORT instead of BEGIN/COMMIT/ABORT. Please
comment on this point.

- support for SAVEPOINT syntax.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"La grandeza es una experiencia transitoria. Nunca es consistente.
Depende en gran parte de la imaginaci�n humana creadora de mitos"
(Irulan)

Attachments:

nested-all-15.patch.gzapplication/octet-streamDownload
�r��@nested-all-15.patch�<is�����_1�&)��~v�,+~�����c�lm�@`(��������g��SR���Es��t�����h��N��M~������(�p���]a�Y�F��v9�C���w�v���^��G���������\�8�������Xz��j=o�����t�����VK�c�V�����l.�������Y{��sp��3�>��?���o�t=2�6j~�|�Y�����s����bqmv�c�no��-g�.KBvc������c�|� m��wk,��p��X�D���7g��>`|�����jg�����5�v���vM��6pT�����d�_����`��7�$���G�/:X8b@�g������qk�);�v�������
k�c�F�
�� ���7���_�\�]^�{o�
���	�j��<����W��v/�\�q"T�?z���\09�0��7��-K����iX��l��>��+F���������X��5��]
6�}�e=�~���^��F���7_�cb�
�e$8�WG���_I��p�p��F�M���V�������Q��o �|�&�g����5�����F��(����#�p?����z�=������Yr�(J'�N�d�C��,6����~��[���H��
�K���������k�M�H~2�8��
�M��
��*e����������$�����;p/���%��UX?	�c/��=�f�:M�5��)�sS��VM�����$�"$���7�L��'�����E\o�w��J����s���}�m8�yd6����m���q����?�b�J�����&d�'i,��P$�>����>O���� ��\5)�9k��'i$d��U?����J���N_~*u������S:��6�b��1lp�;�����1U��������:d��}/��P��"����l�1�EIT��Hk�d��&V�q�&H���c��b���_��!����)-v9�<�m�h����v���*�0�.+�$�6��C�v�f���L�)��\k7_zxD�B���;��_0�AHfM�!�>�"�M�}���*B��k[��������gd��
m���;N���xF��J���5������[,I���p������t�a�E���*t^���J���W�J�Z���CW�[L����z�I�h{+�X�����3a[��K(F���:r�BG����DrE59E	V���E��q;�!6%Ei}�t����"��\Q���M����|�l1�]@�X%6����\Q�J�L�2�3����63���z����'[��kD,-b�}�����y�u���\����������9����G��T�]>�������������������r��O���9!,����d�!�����B�^��������{��B>$qU�a_�*�)2-{A}���s
L
���c�g���d�������M��XP<��X���<0��J4	�g	G�6��iS2
i6Z�e6Z5�����a�N:7J�S�Yb����Y�cN"���� �*>V����E�7]�E��A~�w�fxWO��J��<p��5Es��Z�Y.�0��`Jn
7�t�a*~;�YzO<��������6��bZ�T��B���e<8*j�3S%�[-��~�����X*N�q.���m
�fl�\��(d�N�����)�������8-�7U��O2#U���������
�-�O
!�Ynx4�qM�����g�����h�G�fL�~#-��.����������VI�,�N��K��,�j��qn���g��o������{V�y���-���
���7f�zp>�P�r�qj_Hh���m�b�X�:#���I���r��?1i��/�>����HS��J!�K���������`�P����\TdT�n$U�����	V�[d����-��lk$%�!���� B9(�>����gbL�4�UT�4�b������BO�@�^z�m��c�SGoe�!���+��)8�d�x`�&J������s��t�9���5�`W(�9�v��y�1��b`O��N�P�B���NoVQLqno��"�Y���?�T�D�ka�
Z-LoN%PwZ2	�J����J��V�[�
���"���Q-_���:{���hn�A.5���7���7��%}��%u4Z�Ko���h��}}�aj�K�
�����^�]��m����m�X���P�I��V����C��_t>�c����v��_��)}�U����^��(��T�"�t�T��{���h���ZE(�@�K��Qc��r����z������?��e����+v����y��-2��9�.Q��������ro]%.��.��5��L]e�{�i�.���/X����d(D�;EW �b��J'��HwF���}��s�-������C��E2����*�i��-��w%#���YA�����(
�^#��)�c���on�������U�/�1�bJ�&��1�eXX]���+�M����~`~F��x�od���9|N~�,);�n���������R"e��L%���3;��M9C�����'|�DJ����GI�4F�_��/��"��__��N.��=��:}��|~����N8~u���
����s8;��o���	l�$��x��g�����'���g��f%� �}o<��y���s��������w��*�R.$P��|��g*�y{���
�H�
y�C$����5W}�QT������l�x#�������7��@�������������\L�k������_@MJ���
�������K$�@����� ��-�������Y��_G���^q���gOd{2N�S�)���$��ew/���-:���U���P.-�z���=��n�"����W��y��c�eF�����y�Zf���to�t`$[&�B��s�|�	�z��e���������, ~����g�-A�_O{��W������ye�g2�sg�����4r8����(L��������8
"��uD���w�?@>���}g.Q
[�o$����|y�����������O�=�'��0�����D�#�E@������+d��K���D��9�;Q+(9S`[��G!n���Bbd�1���L�|�����D@�D��H��3��S)������_4��"���>�QHlt)H��@\;�|���HN������d��)H���,@��(�e�;�5�w��/J�8�����q�~�~p��I���g�a�c'u������/.?\�k���������?S�����\���b��Q��-����)�\�hcNy�c��)|�!�7���y�x�����<�/��	�Dj8�/�f��l���������Jo���v��c.�F'�d0L=�u=t������T�����a
�N��u��C�\�|��}L�`����~}�#8���v������h|�W�G�V(���8T��E����J���}�����G�aZu��^_P~��G���J�������}�I'����� ��U�,����������I������`�)�)���>�)];��0u�#i�����9t�+c��GG�I�T]�`u��g�����N9��'z|�'�n�������e��R���?��ta2",'�O1�~VLF�n�D���x;9��,�{,�g�����������e'��� ^�#O�����	:?8Z���r&��{������������=!m�]����:������)����S����kS
��a�������2���O?���U�l�^����-:��4U9r�����2H�����
�G�YUW�]P{�}Ur�����C�f|:����R����H��[D-�h{>��A3sM�d�7E��������9n.�k�d���b�H����H��!V�K3�>��ZEH�E�4`q��Q|j��)Y��Gv�'r6��;�8;�@0�Y|
������ 4?���rt$!"�%�u�-�|���xv����XN\���XV%�d�6W�IrRv���8�AR����|��E�������E�g6��>�=��,h,h�?(��@gV��������hK��V�@�aT����G�W�j�x�\���S�����&�����WX�r=P�������vM���Yv{���DS�c��E�����\��T��0v��#P;6�t�X�Q�8@���>3f���	�1C��B���kH���n�2�W�g�*/������������*%����>���[���y�.X�#&Zl�&T;���	x�0��/�t�`�?�p�Ca�G��l
�<�����+��I8���s����`����V�k1M0�PR������L���
���<P�-���X�'����T;��� 4�=�l
��������,l���@ -�{|#'H|t��F�p����(���=�w �����4��	g$*�T���[����;�;����w�������O�t0�h����5	����uyq~_������_0<.1Kc�L�������Y�����^�	X*U�iK8�0��aC<���mdO��T,�l��$�Y�S5���+�l?ir\9{;3��j�?xw�������A���mz��?}���Bk������c�V
B��&��x�<p��T�:#�������v�$%��	��c��P"��k���U�`�yF�z�K=���w��88q���0��d�B�Q)��� ��sI��b����<����<-��y�k,�du���
�������B�D	�*W�a���`�����r�9���^�[Zi���	���H�9�4�kT����@
C�,�GQ@�:�E������e�Y�/�@[�..��	��v	g�<����5�������R(y�rjQ��/T��GYP�i����
G\��[c�0�h�G�4���0�	�c��D��(G���:�a��A7���v]��G�l`*���1(��b��t�Nm�]B��O��7��r}��U{��r���Q�9��sW:H!CG��T�:�p|�"d�:�R�
��:"���<�N�p�7_������l`u7���B�q)��Z��m�R�e3����}�C�����
;�_P@��;�i7�g`T�4�.�}���wQ�+y�
v_�5I��<}��'�O�
����G�Q�$�UL0�c����X���_j����������"�T)�l~["���>�.��{j\�"l����G%+-�o�R��]tW=y�T����3
�>O��B����,^�W��4=��hD�\yl�~il�Q�f����Eb���?
Tu��fe����?�Q�e���!���^���[X��5k&�*��K�"��������C*�t��B�i��YC$��E��-/��r��P[����t��&rX�/#��
TU���P)^%���m�x-!8\&�s���9g� ��P@[WstzG��h��5�!�4�}�]H�����)i])�D�DE���K����g�/�����r���	I�C��������r}�-�R5
�j�#�>����?�����08 N�h����T��W�Y�Y]���"��j��^���v�~A[g
K����gG�/cNK���`E�Rt�]��M@�H>w�s��o�y����/��	��\R-Pf>
R��w����;�	��J� ���6�[#{��z��z_��9�s%3����H���G����T�.�`�V�"�����K&:Hu�^�GYB��o`x��i)�G����|�n"����B�LX�2t�zj��:<k�Y���
YwA5�������(
6�K]C�����S�����~��w��"_+nz�`�����w�|��^�i�.������q:��B@"U0����U��=�v�"���VEL��6O�T��(z���0�'��&K���G��xG��C{ �iF��D�-��1�RnT�!JK7�r�+��8�1&�{' ��L�T��W��}�s�2
���S
J���g��g��Qx��\���Z"���N�,Gl�c��V����V���Lu`�RJ��$���Rc��N�_5���G"�d��[�o3�B9pW_[�	�������p�7�����z�����L���21k�`���
B�\��n
��Ze[�&ue~���O������
	� 	��!xi��u�%�D�iG8�/�H8�����"���y���'hR�/�����:*�-�T�	��A ���I���Q}�:Bwj������m\}���|��4!-H�NJ��^�D;z�����n��xQ$$�&	 -�����w�Y�IN���ZKf��9s�,�coT����/	k�\vf�ow������'T2�����������g��~��_��=�jxoP�fK��T_/�.��`��P����!)2E�e�|�q4��w�7��t��lc�.*�AVj���T��"��Q4f�����6Y�a��������_��U���]4"E,���`������$	s|�,,$�f`a�;� �Y8�I�J�c>N�i��>�N��z�/�YAxG�=��|2
8��oY�8`"+3�<�� 0{�
0)9��I�S�s!��N~l�T����Z%[�������v�
�*v�t5�?H�
Z_���`���9}=�-q�������hMMlh���^4A�$I�
�9C
������3��PXCC�2"("o�pI�����Q�b#gf�~���s�K�2��A=�����k��P
7�Q��j#>N�����#����D�����\���G�$���w|d���p��[�G9}��E`+���F2�'B��&�>H����&��������������5�)|�����\^8\D�ra;!}^�x>��#�����r}�~0A'/	}4i����^��D.��a�|\z���� B��[K\8D�phc�G�����U�h��6�]�
q>��+$�8hcu��X	o������k�EZ|�MC���������Kw�<C0�1K� ���I]O^���<�R��� �8`�����UP�e�	����C�
����C�Lgo���=N�T�9�C��$�OX����,��&�#����6���(y�fI�w0�>Bj��� 3�&q��O����/��qQ��.Gd2c���h�s��dk����S|j�t���g�G�Iaa�I�����r6�3l��Uo��<��v��IC�����7HX����`�7���?��������
��>/���Vg �����bQp
3j?A�*:�)cPt����I��Mu:)
�S�����������`� ����X�6�/��g�b0�O���~�=t����7w�����.�5���J%���J��"���s�/����������*w��P5���P]������J\���{-0V5M�)k2^K�;r���D�	��	nA��D����t/��%�����n@7V�}�>�4�q� �X��P_���w����	M�HX����9X4��3���YJ����������8Y�)�\�
�_�o@B��5���,���Z%�����Z%������7���Hy����w���k�u�����D#�LN���qurUcErU������y��W���k+9�W�~������f6���(Y���z�R�����9��5�FEw���R0�)�B2�\�(CM����+C�.�wY��>����
c���Xf�0����a_e?�Q|P!6	�z@:EjH�mP�!���H�V&%���9����"�{�.|�����W��Nf���Tw���o����CW�+��
*F�Eu�cx�)�(���\�@i��s�����M��l \j&!;�K�a����Cw`�	au|���<{��v�"bX�24�(���n�������D����a�t���d�P�:����b�4e�3xl�e�lL�/?�n4��_�����Z��4����j���g�U}���t>�����(���_NGA��Hx�m
y����L��."�`J��B�s�y4�J���#y�v���	�$i���1)��l�6C��}�YD4HZ�����d��������[[�8�\\������/�F�K]���
�*.$o�c�g�QyV4 ������5H�4�}�!�X�aa����|E���8Hwk�	!q%f.���84zq���U��������l
O��g/CbN�;t��+�Ay���5�dn�;���$��|~l�/{b�
�������������S�pt���<N`�����6{5��r��������P���x2��B�����k�����Q8�w����`�i�y=����p��C��1u����u{�ecP!���P��g)�����`�����ct/^"[#[;�<��������8K���/&szf��&G��Z���'��^Z1��%1i����M{�������l�ei*�M�Z�g����,�FCo��.��"]��8�:���T�xh�B�<|����u����[x�p��a��b��o����c�RyYm��m ��#�[xe��G���A�h���������
��O�
&P	����9�'����R>w���8B�>Ii�m����H�����;���G�\��V=�VG�����\����)��D�O��y:?��(����T{�x�������H��?��3B��8��_�r�]�8��/vlI�q�2��j�/y��xrBA��v_@O >�O���*^��EL9���X��yIg{`�"���EF�X��`Y����h���2�{����U������83�\�"����~��,��h�q�����O>Q�uxq�U�.5�N��cW�[Ob	N�[��WlN���kM-��t �b�r������4���xb��t5I
Br";���^�Rj-�hk^~�w0Y�:s�Fv�����D��t&0��s4���
(
Y�<l���w�]Z��aO�[2���<g�/��#�c�h��:8�+L5cN��I�z�$-3�:�L�b��ilda�o�q�������5�i23�Q�wr��bX1\m������!_i���������4� �+D���%�	-o{l���[E*��[��;"����3���N�[��ez6[��]��#+�h������F�w��F}���$0�T����k�U�� l������,��H��p:�{5�#��M�e85��w �S�#�'vqD9j�@����MB���y�u�,��3���������}��\�������"i����6X�9���L����������ND����;��k���^�;�7��&YN!J����[�(!��O��pch������7$w��fH��P�����5�"�=~p	�i���)w�O�e�����~��7�XKdH�h��+yz�c:+r�Y�'���k	�U�m��$����L�	Eht����	f�Rz�+��p2�2��O7�
�d�D��wO��3t�}������PF���h�����������0��M1��]JY{N� eH����������+a��/�i���w�&Q�1'�$��,O�d�a&�[���x	Cv���,�
���"���S�������+R���;���)���|����ZV>!����4V�i��^��Wo?��N5[m��E��W^
��Ro��F�8��A+v|��C�/��p�����VO��y�(]`.R�n���#1�@1�9�+�	��*KASfd�*e$������kDB-s�Q�,}\g*���Y6}�~����_��^�H����E���
wXQT�Ks�
�J��-�I��"����5�3�sv�ys����wtHe
�l��O������9s����}�y�����A�Lc�����}+uvz���v�6'���_��:��?Ym9��0.,4��V�a��� 7�JI�JM��|�J��yr������ti��]t�^4�l�������g�$���q�������C�J�L�.�G��R�~A11�+�����;�H����D\�S%�9�i�X�	�+d�����4�Iw�BW�J?cF�UaU��y��zd���7��8q��q8��8��OO���32�-��&����)�*!n�=��n,J1#��~J�'�N\����F0��	\%���p	��Q�z~������9��0(�5�!F������3�Z��d)�f<�a�AU*�����N#�V�m�?(�m�S��[6��%��i�@�@�8I�\ry��y���
��_�V�������3";xN���������*����\]���Z����	���,��Y����oi�)7&��x.��
sk��%GT,5i\Y�9�����V�6'�j����;T�]���cR��^a��B�L�(fIG����b�v��/*��5&�jy��������>S��C�{R�3�F`1~�x�@��}G�/L��^w�
��^�a>��-o�@�X[)�.[O[�}�}$%������s-Ty���n%���V����U��.������!gXv��X��7��+��0�K)�.c��$��T��>Da�[<��]^�s�.��g����Kf��1x;�����.4�
]�@�'#�����T���+�!�jy*�O
LT�T�Q[����|��?v>D}(h���SWQ���i�X|���#�i
����� U����pi)�7xY�^/�u���8�<���F�"~"XJ���vPy���D�f��r�o7�l������|���	S�;/,���t�.(a^uT���CY�#/(ZT�y�P� ?�j(=b!�$�P�m3�m����h��q�E'�M���}8$h}��"Pj@#���VD%��I�!p�S��_by�P�����u>�8n��H:Az&�p�M�
K��a0�Q��)]�i�C,��38S� 0��T���.�
�U��u�G��'3j�$�����l(P��<�E��E/i�Pw�r�����3�u0�jl��C9&(�w�-���<�nMe9����R����.�Tl�D�86���U���E�
/+$���c`v!���u�w��x����Z���m�~����,�����?��?�<;��Q��M�-������a�s%����������:��R���PI�	�D�����3n�����^V&�f�oU��?�d����d���J�0�.bg�!�S?b\�����xL7��P��Q��(�;Ep��F4E���m����o����qy)Fh5��6�/�5*���T�]z���0��Vc��`��s~|����^�e
�E��B����.�P�����'D�N1Y����
�{�4v<^��Zl���-a�!qg��9Q�9f�g���hZ�m��QD)�Zuon=uo��}��
TG�?i�n-��[+n��U7���tk��i��/��[����5�����r����_o>��Z�oif���~��b�������@���R*��+�gadX���bg�
/�>K ��f2���� &��=��TX���n������\v�<�'3��'4���0����>�	��O%[��Y������D>�z�@�?��8��l��(O.���j�!����U��;	�7kq���M��F����|"��W�cPTr'^~���_r���RY�4j?�����Rq~����@r��C��NN��o��@T���O*�������������j�>s�^H�7c5DD�Q��|��>�����E�b��c&�	&� ��/+�=�
'��z�o��Z2�-��.ao�'a���s�;}Bs��/�9��od����[�M��k�s���hK,�p>�z�#D:�I�,�����T��U��,�*Xa���(@����s72�T�EEA>X.iKM��2J��$z���0-S+;�������32$gR��7�F�b�������]rN��O���*O�b8S�������-�]�r}��n�&����K�'�H�Mn"�K0�1��c�
dHROd+�d{*�5��
,���aOMd@'X��5��e+���<'>eE�����������e:gG�����^�C��B����f�J�oV�F��P�]a��C4�,�)����
o{o���_�V�h"�"�Y+����N����[�!@��=�\c:�Y�o�\���m1
M�����[�n����j��o�^���G�%W�2O���UM��Q���py��&��Az�B��I�jD�A��$
���(�|_�
G����;7�bX��QI��t�a2a�:s(&�[D�%���)aSQ+�%s��WX���-�=y�a1_�������\�[�1�,aB�#��J�"��>
����h��|��8'?*<������_��$��5����Ae��0����9�}gRs(��."������=�E�<el�������k0N!�T� "JJ���[v�����r��
8%�uC\�[���:V�
Z)�E�hO��>��A0��a�(��.����������SY���+e�����TX:�
V�yi�Zvz��jO����v�V���{�J��U�e���(�
N��jJ�|�Z4�@����wV�����yI$�Y��}��<�`�]nW����5���:�g�&���7����{����+����1�4|�t��	e���dd0�3��E��-�E9�Z�5�K\hU�~�j���������RN�%���WQ�����6�6&�h��D��I��}�{�Q��H���v�����(�{)C��p�6p*�����
*c���d��+^��&or���������u68�,ZQ�
�������s.G����U�{
����Q���Ej7��M��J�
�^��V��=��Z��T�����v� �
�P��]
�Nj��f�`��W�hM�t�-�\�P!�K+��|�2 0 X���";�
�Yg����I����UX>n~�L1���<r/�s|E���Hc�f�������Z�	W�sr�ep!����\K���rk��;�v=��	����V}mq2���S��a���r"+@����u�A��O'F���aW�������wK���)�7����OS�������,�����9�zM�^�r�����	Vu�xn����b���|�|����[��;
�F���s�Xn�YA*�
�P��I�R���*�S��e�4��0��,�(Q<_wO�S��h)\�_,JoynI�+v%�eV���]YV�R��v��7b�������=��?�?*����N��aZ�e�*!0��F �t#J��S�fF��L��H)�5C`�gd�5�0����W��.������f����nb���?��+���Rm��JMK�_b��0f����b���r�;L.����
X�����;+ ��B@"XQ�#����PL��� )�?�n#����.����4����Q�B���������b5�^3�B�Y�Y<*�5�u��b���^��u�>���n�IshT
Q���?If�S��kpmE�(A���
o����9m�&���������r�Z���9n��iF*���Pw����H~�b��E	��d�/�*_�K�A���Y��kZ�
�A)�ZN�Y��?�)!"[4Ol��_'�,'r���n���V���x��+��|&�E�~J��Uj�
�^�mX?���3��6YS��q��v����
����-?
dm+o����Rf���N��������
��9���%��z�8����|�a<�&���'<=������� ��9�zy��b�N�;��%��[d��QL�z�M��-`��T8'�p$��
@�@�����K��~f���n�.��3k���	v���
*��bR
�lsn}�EsBm��M��|���x�\o���^��&�
��\�
�Q����p��T�����4F4�mq��~�jq�����������o�(fU�G�*%�\�Y�n��	`ub���j����u�_dT��
*�&��u�?hS&�^�`A?�(�1����4���������#|[JA���u��Q%[�����w��-�0��2>�$�9���#[xI�p5%
)h�K,�Es{�|���n����Tl{���Sq�]�9Q
���=>��dFT���)�v
���[��pF�'�1�������i�?$�^��	�;���'�3��f9�-+��`�jxbl��M�����h�N�q�.}0��,��j�R�]�qf�����"�b��RxUa�0��j����a�A�����-b�a��y<T%%��x�6����'c��dl(O��j���M2��BJ5�T5��p��hE"�x
�n�D���?��c���R�e����g�����a�0�TL�������C�y�����z����a?�=���������,������:-��6_������������K���n��4xJ�����}�I\�e��d�����q���=/�f��@2���������;���41()�T$����Gn|�u���y�p-2��eT������Js�a���b/����b1�O����f)��8L�7]��]���a+K�	��	���vt����Sr����w��%o��w2�T��������w./�/�V"�7W'+�����Sr����v)�Z���Z�3��mf���06����C
n:��f.B
e��>_Z��_iUk6�?��r5����y���<�6�;d���v�M�l+q]�:���[Y���=���<�a���Q�@8I�a�:iwT��8]m����G�m�������Q gh�)�.`�������d(��F�HDCB�[:]8
�|Q���Z_:;�r��C'8R�
/JIz�5��_���	����lY
b���Y��@�C25i D��*�;R���:�T�?6:���(k5�T�����G_��-��E����i�����O���l�KC�=��L%����@�#c��(�g��.�K}�nyy����u�G7�����jC�����
u��
U� C�bI:*�fh;�}yQo%1�1~�����N��\d�U��,���7�Z!a�K�&3��}s�w��JJlU9��I ]x&�Zu4����BGw���;�53�S�.�.
����*>��[�x�I/(���<��6_��TC��K�_���$�	����1�z�,��c�7�H�U���kVx{q�P�W�~���1����dh����D�B�LV�5�����������LV�6G!��n�l`q�����z���K��u��J2��r�r�+�0����*����]����$��@��9��w�p".�h�C�}a�S
�j�R�������o4O�N��yr�W}z�^h��sm?O9u ����K�:
l\Z����RIS�x
�����Nuq?vz�����\IU���8�-n��EHq/��F7K��%�X�Y ��(��l��0�� �/�5�~���n�I�F��m.=��)��%G�p3s���e�whI@��f�m����PK�����J>o�-G����
��>�>��t��l��?3;�4k~�Z."�c��6���=T�<���3�E6K���=1�
��@
x�n�`�� I]��2��5���(����V�+�;�3L���������#��5���+�n��=�&�A^iV�T5P��U���Zm�AV��
tH^SzV�z^7���G��"sZD;�1Nk�T�u5s��`>���^$<c��5�,`��i�LBv]�Fa��������}�H:&
�
��A���d�a�m���O@�@<
��O�M�n��"I�,����$�����6���p�����)�z����Tv,$c�gh���`zN���)���Y��\����Y�z��a�|	Q�3f�!F��KVY,�T�M�=�+����,��g��4�����h�{�������0��n��]��3�Ec�"����t"��O��]��F���C70����o���>`p�)"�V�����0��+Q"4C�����z��Y��N��[�{�
3�R&�����tK����L|���|����FZ�A�=��W6!
z�>U����������z��&�Q��W��"����C����-�of{���j�����Y0���g����K9�x��6�y�$����!q��e�L1��c\�Kq��#M���7�y�����8�h�Z�O�Q[�7kFiqv���`����b-VW@5��m{��!v�W��n����e��
�Mm����
���yJ���[�a�;
g���B
��[��� ������[�)�*�x��*�����RF������%L��-W�������I����l�Q��
N�+���=w�-�������\�����P�l
�T�kp�T�������G_����; ���?�����I]]ia�]�fd����U�Z��>/�
���%��5Q���VA�.p�Pw��8U������H������u�;�����!�KJs	B�`�[��kx�	:���o&��Kun�>h��8��T��3���r�R�~���<���7tX��zK�:<��u��.W�R��T2��3��{��[E)�����&���p�h���R�n_O�o��t�0������l?����?���p��<�����N�����?��M�����y����x����e�a�x�j��t�&p����Z�������gv_����\be�r�'O���'YoE�����H��/)���(B�}��qF)D=����'�m�	�[��ss���M�r�	Z����z;s�vx��K
%����Y���
R�k�I�=7
���&�-/3�)�\��i:y��C��Y��`nm�`n����n)�f���;���F�z���������'e0&w
�+{��X����D6w�-3T~>$�ac���G�Hi�M	0%�-���$�9��f_#
�e|��k��y�@��xq�q0����� &W(�#��5�C����$����v���w�3��i�|�������4�8��3����2@�{��L�ry,��aL���R�
�f�`�'*7��.��L ID��������}���e��4r�W�a��]o5D�@��C=GKc�W�sA��k�w��H6aQY����,f�F����K����9���Sz�}~r;�
��wW�|OEbXg��'������a�1P����Gy%(9>�(��4J��z���+^��Q?�|�u����.y�L2�����p&w*��#�Tg�k�+�u�C�k' ���I������2]')�1��kB:lo>�����vV�����B��w?� ��O`c�i���"�bb�G�z��?����x�1*��e���AG��o��;�y����Oi�sw�'��s|.8��	~�:�x�S?�-se4�������� ��9yC���}<^4�Lv��c
��z������c���?��*����������c�8�`��+�d���KZ�N��i'����K9��\��/���[&�������6�L���(�A���2Is���L���������A���z����B*��::N�bb9�'��#L�smMn�1ty(��
��B��t�!����^y��@J0	#���l�zOb	�)\o��"����-�#f	.g��v�	eZZ���R_�+��43��o�D�@q*u��)��R�!|L�e[���^[��lYr������c+�	�_/S�+�Y���R�� A ��
D��OR���Zu������3����B��Q5���riz�g��E>���E=ZH �e�YTP;��^�E�����n�0��
,=���N�:��4�	o��D���To��6E�14u����3�)�_����p�u\�:�2�4����aL)�����������*NA�S ����/��XLV
^e4�f��jd4Vh��0X���7�&����&�Y�j�k��i��E�t�b�y�����\�%������i/�����s�:�=$���<)W�2A��?��Q5($����$���NJ�+yNA��|��o����H���a�s�9Bz-����l�����s��
5��Yl�q���8��%�M�C��}�8��J?���Zd�b6F����oZI�0�D�j���4����vqU�ITd\���2A<��~���r j"���*��FT�x��_r�T�|K]3O�Uf��L�n�;x�������x�]�Jf����yOE�uW��,~�������k4c�2-��lS�
����p>-B�\�cnb����y�*�u���D����l���f��)���S����P���(�?[\o���248������3�Fa2���t>���d����n�c4d�d>�bj�RU�+�zbiF7��QH~X^�[�7��q���,� 6r|#t��C9�dj*�.���Ah�(;�@�]����d"�F�a`������!���d!M%1}1��>�1�/��W��bb�G�>V�<+i���K/�-Q�Q���~r;���})�.��z[[��j���d����K�9�_�i�W�H���W,I�����6r�]�����M&T��l����;�����)����35_/�]"����cM?����t�k#�Y��#3�G��&��Y*��Q�.2�)g������M�(y�vd��(��_��}��q$��JB�mM�0[r����(�9�
5j�' a�������p����5w{o���C���������f�BF1��^���'���[[������w��{R�^��/��!�9�E���o�a4�����"�MGi�cD��=�[*��(w��^Z���,;�fV<�ueC������?���zg��p��U��a���������	��?�7����D&��n��Ww���������n�?��b8	����"���0$-�S=A�[�.���^���Ss��$�5�e~��)c��@'��������/�G�wda�����a_=iJ#`6%��C��e���F��f�n���-#������Wi�/���u4O�����QJ��t�����y��v"�����)�������p��G��a�!U�.���}8Q�c�M� @���G��og��q�+��+�#��.�`x(�#�O�@���no>���pj��8�?���-B�����)�/��\0�}���E;J����e��
��������b2?���A4��c�����x����L���������[��E��_��1�j���"��A���<j���I��F��v{��M\��xg���A�C������:�T��n���+�����5_����-�������}6���O���F��W�b;��D ���Q��L��K^&���������O "��J�?��>./N�c���r��]nn�k^���h���;e���]���"��:}P�V+^��W��+�2��8^��W[&p�_U�Y����I���8�����h�jStg����Q.�J���m�j����E9-����sX�F�41�Z�&>R8."��u�0I�T�&���e�0�2],sG���S���^��������p �(�.�*������&<�t��O��zHE�f���w���L�(!�p��F���y	���'�9��n����~�f$�=��Y7A����8���C��}���Z_Z�w��DssT��=������u���l��z�j�����#
��9;�^_��������o�R?L���h�����E�t3d��|��lM�;$/���d����K�1�t9�p���W)�U[.�������>�m�������y��O2C#>�9��M������������/\g���B79�n�)�2��>Lv
SN� �Zp����:��n7��
�\G���
oh�!��D�PjB��i*M��.�@.��
_Y�~�En����\�=�^� �8��	���x���k�a�=��p�C;lH����Q�6��p�I�������VKm�������h6&��L<!z�u'#�E�8���!��_?p����'�`_��$�YJS�@�b��Ja�+	��I"c��MgT4��g��t��K
}<K�#�fQ��A���������>�Wx`|��Y&oGDL�$�A�k�T]�H���fa��[�������\�V�5�z]�~�k*������Y�������a�����d>%��r�xE���'V�
dE	B0�$S _�Ix��>��\��#��@kV��`�2�lc���/5�1Y����dL�g�7 ����I=���:��������Lt������~&��=��o��p�ioWv1��t��,���b)x&���0���E�����pD����q���������:�Q�w���qx<Od����L|���A�8������h�O"q�|~�$nK��cq\��t��:.��\N6��T!�r����}q�<\Nl���Ht�6���7/��kgz	>\�O��hEDBe�N��h�wp�a$�:�)��\�Y�n���6+F&��z�-a������Y O��/<p��0Y��c�^z�1���J��U�{��#/?�V^������vW���~����?5���E��V(��=�����dV��o��(��5��~
���$al,����Ksx�C�B���	7�!�z:�**�-<��M�C[-�w.��������7*0U�l�,6������*#�5�lF�����P����S���cz����"�9�"�G5#F��)Yg����X�cQ0��~�%��n�`�����>�p4�	 ���JBbd��D�K��xC.O���j�sk�--���"J�7�od��-7&��0�^$l�����h�z����s�O�@�6�,(*KgJdq��C9��/��2j�%�Mr�$��d+�@v-1D�/^I	�����@�Q����BC^iS�?������|O����5�F���w����������5ez��J!Z���Y�$#�S~|~��i�����,�=��Go��"�5���H���J�����K�|
�A���:���PE����x�"�t����}�����}#���]�
�8a��
��o�����0����Z�D������� m�t�i�\�Dl2{����<4%�Z�r
nA�dw�O6�F! ���2�
���[�����K��os��ta��EVee����,��:�t�R4���U�;�Z�Q�7��Bl����t3��B�oo6��`�
e����1z)P�.��:��x$���2�C��o�g�x�<�*U9���c��}O������+�0�C�'���Pb�=�|
�YPL?�������$9/���QA�*�t$�v��7����uC���a(������@��n�lm��4�
C1�h[�'�s1JO� ��������
�2%�	n�;={	:y���p�D+I��(K��c�6�O��'u�|�����d������w�y����y�b���������UG4"/L\��^g7�z7c�����	j~Db]q���+��?T �UO���PZx��|��qL(���d�Dv`R#���
����S+������e{�~8���k}�9/�MN�$�Y_���q�+#�-onx�%Zt�s�A��e������#L�������V������-�<,f�(���}(b�$��	~S[�|n��~N�c������������32�A5A�	#��5P�5����U&����{7��w������FI����4��&?M��pV�&��E/p�Tm��'������2AM�����'u�MI�|>�h�����Cgd�!�|>����\�x>�yEy�r�Ds��,P������w|�������H����X�pH��[��agGNj;���������H?�u"���	I�sY{�$y90fG��sl��5�J�a:��~L�+�g���1�4F���,����k���!f�yD��OD>p���G\���B��;3��H��	�^0����<B�%��[�/��W�������dv&���v|�K�up���I�����A�7����9�z~��n��@h�������7������1�e��S�����+}qz��R��3��U�#.@o�[�V�-����e�������[�*���K����U���M���-A}iLB]����?dE���z�t���R��1n����)�!��X��
���6��_1��#�#��"��M�DN_�s��I�Y�T���d��ij�9���Zc�
��~b��� I�Ln�Q�[����yr'���_d��pFDD��8#���Y�o����3a�}0�X�#>A��)I��N�D������K �����=���8@>���+���XB%���,
��������wx~���<8>���@���p}-�~�HD	���$�I����<�OLJI4�c���v���)-J���o�$�;�N�Utn��36��m��S�d^X|� n�*�Q�0{��(fc�=|�Cl��p�Z��$2��s��DT�
�r���f_�D���a�5b�s���{!]��*������F�8`�p�����7p�����|\0���btG�-xwr������2�0hQ�P��*�����;��6F�1*��T/����H�VQ}���/��m���2��<Yy�x��&g;1�T�L��y'utO7�=����W�"�q�}��s�46��P�{���:���K�W��	�W�9Az��%G��>$�ZZ`y�u'�#���+{a���`I���Y��	WR��bJ:1m�w%���c:���~�;��s*����ZC)�et�EC����0�>]/<
��i�s�t'+���Y)g=�N���0�Q�2�����\!`�m����Y�QE��%#?��h�H+��1��BN^�lC�Hg��B���%�F#
���n�V�#6�~�����v/y�����?�,��#���E$�7��S"�h�������\Dt��H����^��nNlw��u*���������R�����������d�&m_6�����&�&�����CB�k��r~����
�u�(������w��'�W����>X,�LG]����<~��s��W����������2[sW��\��>��T���KM]�k��_f�*�^��_��!�h��a�������6F�Y�/25�-��H��HS��
9
�J��`8d;r��V��I���U��}�
y@�A2��cK	h;i���nhoZ�Y��SN�I�8a��0�|�	�t�(�-l�����/��L������Z����������o%�cb��7���a[�~>K=�F��r���$�p��$�t����B��Bi�}Y#]���mX3���V��]A����46�X��S����*;�����[m�R�\w��������~��&�(����f��:��o�.�G�
�t�Jm�@�����/�D�.�	��K_F�LfM�;�W|��Q�a�
���JQ�p4]�����-3;�����7�x��&},�>G.R�_��_d
]]���4����*��q+�����jT�ow[m �����[����M�)��,�����h�F�E���z'
/�RH���57�G���(v�1F(��d,'�f~���\��������:��t�g��Jh�g����J���%7]bO1�A�	|w���+:j������v���)'�7K�d�5#��|i��v���@�g�n�����_r�V�G3��7��
}���I��v���d���*�s�U��K�&�:��F�p�T��i�as�	3�)�����d��Qf��	,I�N�t8���Lxm����UM`�Wo���0	�m6�X�Ba��t��m������\�����M�U�G��Ps�|�P|�p����=u���dt�B��o\A�ugES/B06���<��*�$W8C��)�,��@��R�`�cG��oq�����2`D
�<%t����0>��q���R��:�`v�GO��w~�Q�NP�_�s�?�i&�] �JY����dg
�$������n+���8�Bj��ev����$W
JI��_G}�"Do��@u���:tU�!�Eew�a$�b�"���q����~�k��5w������C*�@�J��!�����
-����9c�`���e<���Q54�����M�����E:��P��Y�"����b>����_/����������,;�gQ����=��s�'3�G���� ��� H��!����$��Z���j��/<o����g�*7�5a��m����#��y��A9��Gi�F��]�� 9���Tk�^�����d,��a"��3�[�=��7�Jd��y��L{j]h6d�5R�_7be���f&���s\�q���Z^�.�[��0���������X4�:�
'���pY�Vj ~Vj���/��-���,�r��s��ja�s�����C�	O��N��K�����D}QsG����F����xhjkmc?[(�p2b.�O���d9�"��0��$r�����R�0<�t ��{��B;$��z�)/�&�8��B���n������.wprr~��\�,V�������:�r�\�$O��������dv��o���H�&0�"���}r��&- /����Tv�z�����L�H��
�m]�M\nk)4�p��
���U��#[��cO��:���������B
h'X�w�'�����-�_vg����M��R~�m��S6�(���I2���g���N�P��2@� �*�L�,#����6l6�u-/09��eX��nk�w��t���%������t���b*�@$8��*��uY"r����I�]C�dj���_��5z��%X,Ngtq3��I9�3�pN(��yQsVl5t�����dsY��b�b
$b����-�:Q�N.>{)��D<W���Ip���f��d��X_<�aco2����AhR���x�a���4��x�I%Zv@-pl-��S��O���'(N��F-�^��^���7��!�#-����,�:�sa����_1XG�]��p���I[���W^u�'-�'�bp��+�������'c��^�)UX���]��+�re#������SR�L�$�yx#���[����P�|�������1�K��}�7��>����'iA3�n4��6�H����2&;�JF���\v,����������Q7�1�3���hR��\H/�p@t|n�L��8KYt��<b��O�����P��#(�c�����j!5�/%��O�F����i8����@�26dm��ZW9}N�����s����O�B�h��C�X�@I�b���K��L.��]1z0�aE�Q�2���NA��9�T��0$�5
'ET4tzf{�UL�<�^�������G�0Q*%�8�3���87s;X������Dyr�X�����1�H=]x����V�mX/JF������zW[��C
����*���.e(�r��S�Z�6���Nc�w�QD�r6jG���D�s��=9������_�$��u ��C�[�3 ��
�/����,�Y�|��&�!�%����������H�4���g�]�w0�IJp��EH��$Zt��o�x�sk8�����������|oQSRR$�����=��v_7���"��>\���KqUdg���s�
vk~�Z^b�P�je=��	��
�m�`gF�l�1�K��tO��0��p_��H�Q�T|����>S-Wq f.�z�
�jFr:)W)��r�/P�����	tp p@:��R���L�P�O��;y,V�3�wSO.��i\R�Re�(��^��)��TQ�4�4���a�*D������2,��������K0cJ*bc[�q_B�.��s2"�1C-��?H��(���A0])4����?k����+�����SBU�����Ecr�a��e2��\��2��0����~6e���7&��
&��y�!R!lem%��^����~���Ip� w~��Z���F��������gd��:���8����%4�����Y�� �i������'V��4��&��[�u������}�?S�!�7�?�Eg}�B!��f����YMzp�T>����N$�a���Y�P�`%u���A�aU
�a�7T2=H���Z�o�8��������[�l!6a�">��'| �x�&��p��	n����43��O��&����Z�t�i�sZ���#���"	%�����'M���d�,?����(������olE�� ����[y����nF�d
����"��(Q��bIv�K2�)�k��Y�
��02&��b�a�(�U5C�S*��ZS��m�����w#M�_ E|Ud�����^��A�%��4a���Z/2<=����;�-9� ����u�t����������Y7�e��T(E�aE^�k ���Zlx��r������[7��[@���	�b���q8�����D�$��������1�vV�is8�h���4*
W^x�b���!;�^?x�tp	���6\���s84�����`.0�����Ug�P�	�\#��#��,��!B�r�����NnQ��`����,Dc�lb�����,����_��5pn�z���*C����6L��>9�������+�=n@*�1���
�*�R�����^0A2������~�sU�
��f��@e�7�+r��,QI��]4@�);�������)����m9����&����4�l�aZ�;`�e<��gB:�������9l�����P����������A�����QB����i���H
���%����$����O���)|~n{a��WG���0����
Lp�ee����)��%B(K�fA|jD�
��.��,�5���G{�h|A���%C6��~9x}����r���{z<��UczZ
�9��%^������hG���f�������5"���I����}����Ji�d��Y�dA��������Jm��~d�L���Wi�Uw]y�������������)���y�"��`���m�^������S~��})Y~N�O�O�O��������������m�]n���!��r�'�,zxr�V�p��x<�H��dY,Z�����o�m��}?���y���,
�1�SL7x4:���`�@��&h�S���Q������\x�H�V�}��������am�w���h����uz�.���e�gL��it�}^o��zKe�63c���$�9���q��J�J�M"��0B����A�b3=�T�6�^E������^yo{����U������G�|��ALA8���?�fa��8�"x��2p�g� �������{����������J1���mq�F��{~r@����q�����������p��6
u�H3�Y���l&�h6e2q��P�{���:b��'-U����C.����?`+���5;���N����8yY����������>�����4\���ry�.�t�&��u��n���+Z��������8�I���{���s�@���oV���'JtQ��l>����c�]B��~�,���?;�/������<���@��]���W
mv1��L���(�����PXtg���7��c��)�`�BN���X��`U�:2X��%,?�7��g���	��rL�=�9�)b��Zr�]A�A�.�������yxw�y r'���"�I����2�.�����d0'����;Fx��6\�����#mI�����fK��ya���Vt�����3���u���#��[����7�����_����2�3�w�o�V�	I�\ER�g�I����$�n";�*������?;�Z����wI���M}t���7v�>q�
�#��v���rY�����-�~<z����Y�N4$D�z}0huG|��S_�	����Cjru��
)�I������*��,�d	��J3]���*���Nk-_���m������UTH�&���Fl���=0�����tq�+$�	�n���e���y[|�:���E�F�\h������/���Ev��E&����.&xK$^#���Q���8 ��{���4k��LLZX���w���{~)UF���+D�TV"m+(!XI����`���,��t����|P*�����������,cta��c�����:>9��_��>�'6�i5��|�����}�HLU��y�q��0��r�9m�L��!i�Z��+��ej�_��m>���O��J�^`��W�i.������E>�_L��gZ1����j��Zq`��c�t�J�h����S�!R�5@��5w
R�r5/C��[m	Q,�	I&E<b{�I_^�on��o�E���
y�����X�*��je��X����a�z6�{��JW�Z������g4���<$/1����w���$LfC~�R��E#����0
�r�t��8L��8��_��"�{��%�7���Qo��v�> �D�!�s	No����e���]���[���t�)���S���2xM+
����YF��0.�z�E���$���D���:�����,������J��W�^���sr�#�����[&,m��`2IH�'4���L��5^K��U[_�S��x���
mpO���q��+����n���b���O�}w��I���qsz���p����������[�q����u��j�g�'��"���j�}bSf�w����*F3��p��
{~:���$�������P��m"�������|i��Cs�li�>�|�'��,S��\Z�� ��l�=F��*hW[0����Ue����r��:�9cL��������vp"�����a��t~:�<;>{���M�
��G�{��.���x��";����%��g������CO^�?���������M��P�
�����x
��d:8��~�����U�����-R�R�d?}/��p�X�W^�s�R�g2�N�����]}�`C(L���N�;���8��;����������?<y�E�������l4" 8�F�{����_NB����wh��d�
������x�����4<���s'iXn�������f����Q��
#p����V
R�)B�Y������s\o��3�W������['J\���Jl�(�P���1��	7��E���4}<��1:Ce�h���S��O���hi���;�B�z���	;�1�5���W��vaU�q0F��;J�t�@�MK��X?t��LL��W*����:X:��}�Z�!/�@u�J�[-�N��k9
u��$%t^��b��LF}��)-��vt������uM5����S����/������:,2�i�
����p��������5���H�.��<0%����}|+�>�'��$ �Or�����c*�'
7]�����H���C9J?��3S�H8G�����1��T��"�"zvF&'.��|!8�HW�sf�?��!�!�P"�J1����Q4�
�S���Q o�8YGh��P�����^Va��,J�xc�I�
L�I��3�N�$*p�J��-+�	�H�-��f���;����E����|8I@��S��%��he������4���<�-�y�g��<K�7c�1�p���ZN��+�c�����)m�Y��7-�U�	l�*��or��G���%s��8�����a���t�t�^���D�1e���~6�o�/���\��[��Z�����{���K+^4
.��6%R������f��C�z����!��*"�
O��k�����������!F��[��R95�9����������fo$��}d����������p�ky3�'w���H��o|`��&:�M���&PmC|yA�Q���'kQ�����@�e��NuI����Uv����j�qj�L��;� V��1%���n1W���)Y:��v��lL������q��J��S�JS�E~K�N���e��0e�LE�*[�<��������8�z�� �je��'�ou������-�������1�u�������.N����2j5�b��Tj
�R��s� �F�I9�s��gh�6�h��_57���c''!N��{Y�^e7��j����h�5j����Va{V+{���;��W
�;��f(
3L |)��anO�k�f�
��t?y��"`�y����i	��E�ed���J*g���"�n�� ��p	?6h@<��+��G|������~�6l��C�(���Z{(4�,���q7��[W�^`������kq3���R�0��1?�gw������0�$��}�3WP~���W.�u|e����H������"^�T���~�9���y����s�����?S�_v��h-Q$d���&p|������hHj��U+�umI�"+m*�s��e{8�����!���nB� �Uk����*;$�p/�]�x=��V�����#�6��:tN`����/���(�f*l�������4Ca�VB�T�T]A���e%{�P���GoJ�.�A��;����p���H���x��Z:���t��	���>�����X�I�����L8�7G�R����C"�H���g/���&{��w��S�����P�M�U��< �������
����N�?Q7
O�l�&Gz=�n��p�D��h��`���m~�*�Y��(���P���Pf���&�
��
fg���3J����9�����-gfB���>��R����:f+�(�LZ�kO��%-/=��j������?�;��fyi�� �����u��6,F���5B��vu��S����D����}
�cKsm	`.��R�8F^S����G��>���>��������\Si*�HTR9�I�W�[I?����R�j;�K��?mnm��|�)���?�7�%pv~�{��q�7��O;x>����p"V���WlqD��Z4k���Z�}��W-�:�����1YO�{�c������kx~+5����5��m�	Y��(k'%��K)W[�-�������L7\P�L��'����r�Bl��vIOY%���C���$d��Jt��chy��=%5{��$H-�0�&�C�])Zg2-.�,z�c:��1��GR+�}�7��*��3q��A��!�	�=�����|��W�vy��Q
;�*���n�s}
����>���q��"P�
[��V$�u�[N\�������!	gRNQ������"�A��si��<��&C��o#�z21��
�qk�~�R1rj~kW��/����Qt�������?>����x'�
m�HI���a���s�p:V��E�C*X��*�]���C`��'�7�O���)�f@�P����K�h|�?��������FQ��k{��^��W+?��m�*����U����7�|�����������KI������ZC��x/
��"��B�7��2����JB�2�Y�(d�l&�b�Gv��+�2_�w����pu��P��o�9��,:~X�;�7rx��-�H��`���
|��tBX�?p!��1~��1�����tX������M���D���)���w�:��z]�0��AKcP��aa���-���B�9e(LI����+��(������/�d�'q�W>~m
�5P`�����& �#G*��O�]x^�sr�F��5��nP���J��
0q!|��}�d~��d�Y ����4v��d�J�
@�BCdbfe���p��t�����~O!�,J���z�_��A����k��+�����oA
m�nEJ���a���JF0OX���D
R[4����^p��,�^��I�G7^17���}������g�}��W�}��j�����W��5v�j���>v�k�>�V�h�v�D�C����A�d�zT�)sq���@x�2�)��#������>x/������WD������[����w����C�1��8��EC�'s+B]8D���~<��:�7Q��&�}�$�oN�k�`2�f�k����[�i'�'�����i����#��ms�x����k�&��LD�}/�$�~��������-|��G����"���|*���?����aEA7
������[X4������ppG�z���L�r����hf#����A0��-�#�G����'�[��El��&�iMeFu���4�FZ4��D���W��E��R~~u���2�����'
V���_`�g���xR�.z�`���IkGY��>g
��R������hr�	�1y�m��������1�+���ut
����]\�]7���
���eG$�K��b�l���x�	�	�����7�Q��~���o�9P��������N4o7��gN������-��9�N��+�
��m�T�"�2��,@����_��}�s�{B
�R�.��	�jjQM6����_�>�G�_O�_G�OIn
E�^y�8x�D�3������c�a�����@xK�U����|eX�RA�C�W����P����J����i3r�������G�g��l2�K�B���V��~��M����*��X
?�!e�_����������-	Dy�w�)�@%��Mp����L�p��?�m�%�����_xp��]~��6������������}f]c��e���r�B��`c4�������O�h�0�C>t$�MZDv���r)��!n��K�tj�0�(���f�B�y�����s���G����@*'�����V�{	��!��(�q>��r_��s �S�A�[��A��S���$S0M�7�r4���ue+��������Ph��$
T���`PK��{�:�%c���,"�
,�&�r"fF��(%!1�%���RC*G����=}��%��q��\�\�OkO+t�I��-�AgY��z�y��$
�&�>��Z��GX�bm�$�����21�<�����U�2e���:1�aG�@o�r��LXJ��f�������Df77���xd�U�c�~�\[;����	Tm���[e�Vq���]�n d���Dq���-�����eXH���:o��\w!��n��y����~�
�d�.!�r:��]_��b��\���]r�@��~�^]8�����c�nw����Dh|f�cc�����wf!���C���Kj���E����ZY��y�����i��P>i�'�u�0���k�%'V#K�����f4��M�S�WD�ax��W��2��(�|pO�F���^�R����gM�_.�������N���FJ&q���<�i�D���2��K�v�6`A�N�
I���2�i�Zoz��`& � �L��B���P�/_ES;-�XS<b8[ G#�A_B��u��y0����v{���?(��P~!�!�p���o�P����8���
�RXM�;�1`�I���1�E�����X��e�a�P"�u �N����2�H��H��5��.?bqw�ar���<z�a0�A��A}�������Wb
������1���%XY�4077
' �m�J��\�s��p��I�EB	�3��n��(����VR'@�Y�#XRa&\�|�4#h�B�G�x�0JL��m�}�u��+����c�C���?�p?;'A���t��x/�t�X*sJ���_������P%�))�aw@���>�z��k��O�K��%�u�+�(b���g\����1Z�_v��K%t{M�V�\����A�s�ke����X�P6r���� ���0�z�t������xB�U��������1{H���e�����������}C�bJ�"�����>L#��s�0����ef�W^1K��bM����Of���nC�y�� FqN�&g�p�r�x����.�l�0��_X/%;P�$�-1	��F����t�7����5�	���kn%�{�5_+�q��S��Q�CFn���R�_�`va�{q��z��e�*���R5J%��<��$��a�G�c�M�[)q0����9"�|(vOo�K�����:'G��-=
G4B���_�Ll�k�_,K�.)�3�*�jI���81��������{����k-�Oy~Z�?�=u��X�������hKh�Kq���J'�k��Z�jBmW����z��E��=���`tt���cxAI���]?�c�����w��K"��@��xu�������WE��tX�`8����f�6�+�e�>�zt���k$
�����+��@)D�Z�'xFzK('{�������i�z'���P&�RR�/m9�Y���������
Q��T�*LL�(���-�#VH/�4������q��<������O\xs�_�]O������gl \]�IxA��d,��$��o8s'��UDG���\8�L�X���8���P�6�:*8�<d2��yS��UD�@7W&:����N�3�e�Hq[���������t����Mr�rc���$�1{�
�������$|��]D��e�*c�E.Lt��\HQ�'K��bj��Z�V1�Z��*Kx�C��3�r�J��p�i�lO�j���.�O�*_��Y����P��f�{~������LQ>BK6�{kE����#[[��R�v��������L��b�;�����X���\�f�o��oA)�XQp�&@I�<Z���($��\]������l[[fSR�n�B���t��/5_Q,�����g�h�R�i��S����b�����{��^}M���^������k+�������[y*V����9�:i<K��$�8	0�YI���x<���C��5��{uz�;<?==����/y���|�y{|��%/�ON^� 3�(�`�������$Gh��Y�G���9iVk>�c�o6�
x�9��FJz$���lM�5�U���pU��O�1?�Gp.*��A
�W��U���qwi����*�8Z{�5�!�+R��W��T+�r��hV�m7�~���-3n��xn���A��0�RZ��,{��z��J4�7�tMe�i|�)P�
3���n�9�R]���iV��z�^�+d�����w�~�\[��F>��h����|TZ�����F���*���Y�/�n�9�6R�w�����Y���N�����tr'������a�s��ny��g���uR���L����������_�\�����&�9���3A+�����A4L��h��F�J��j$�)9*3Ht�A"R���Ix���Dbl���4{p��K����8��r2h	�����Y�n|LHD�6&��2G'�����|6��v���������SNy,�(+��C�=>y@E�6i
H=D_%TF#���C�-�Ky����D�.��x�+ ��w�F��7v��jk�MY�fv1�Zh4����3,��u����q���� �c
RKp5�R��C�X�Y�R�+�5�����j��3R��o�����A��6�:,���e#�i�2��c���gZ$@J�M�"z*���dQ�2�U[o%�R6�i4G#LZBP��h&���Z��E6352�yD�+�Ad�u&�E�V���h���C�������;^�d���x��e��������<:�;���N�Y�bIi���z�J�2!�����o����G'���'���-�v�w��� UBrc��0�mPGL����~����^{�|aK2�R���H��@k#�S�'_�����kM_���=�%��W����������2d�y��>�R�J�R��E��x��OO<cH;�<i�3IM�$�����d�6)���d��2_��$�~��({���Y����ir~�� ��4�2�j�d����GA=��|^7�#��b���s���5_��z�Y|�^E�P�����w����s]����3U
�o?M��,-bf2�)����J0������������&����m{�_����V9�l	�p����=3�{ (��!�`j�Msg��;�HsfbIT�����3g
3����N(���>uF�;��_F����1KV:�7���yk��?eWk{��+��X�����P26�5��*��);�������{���%;E�b����>����~�&y�y�1����)u�����Yv����������
t'>d�b��il6�/�	`�u��`<�=�����r��4*eS�j��f[�Pp��+'����\u����
�����Gw2�NB#��h#���d�3����%U���n-H�VPV��k�+���R�\��*~So&�l�~� �5�h�(���Vk����Zm-o���)�m�x����W�={��9��?~��y|�ES2sN�,;%���a>#��o����������������`�3v���$���.�
l���=s^�6��O&����7��4��+WV��6�~�f����p:G�2��5� �4znrN�8	KG�%L�Q��0����������+�|Aq��y@�� ���*�
d���r{�?CY��[��6B���	{R�|��MY�R��`*m��;����w3`�%���S[��%�������y���#[SL/������UbM������������B��I�3�������w��&���r��OT*sn�V�}���	/oj�v�o��7D~��N�4���ipFT[���8���!	b���h�Ji�/�t@M�H���{/�w�V��4���������2l�4��%U-_#�Y�������?��D����Dk�4���X�f�< �?��y]��(��"�x;��0&^\���z������4)���F��$]2�����k8��],v����||�m�(���ZP��,���;[�lAmb�H���t��,!B8�����*��u�����p��]t/�P�9���L������P}G!����aB���U��R1B�[�O��IT&�;���X��k�%�KTl�2�1���/�Bg��=���K�x1�"JD6r��b{�(C���a�]7�*���O`�5X�j�����&��`��_Q����o�
�
��������+.����7r��*Z��BJ����]���[�+5�W
l��h�l�,�:7�`
��� #����v�Q|8����m"0tE=�l:)���41l��:�Q��?L�c�^=lK*��)<`�5����	,+��4���y!����'��o2!+
V�����	��8����>cn4��_i�5�5&��*b�P����n_b~��C<Q��Y�'���_v��w�:7���������9��\[dcpG��Pd�c��0d�-y[�����K�{!�Qn��Y��O����k%�&(c�x<��/�����o��=�5���n_��[R������N�����k�vr��&(��+�J�+_����p�P��B��>������_�yH��|�:&���f�	�]n�������>GK�������B8;����0RZL��H
�O��'t:
�l$����Bb�2���rc��[MBrN2��3O�8E����c���Ar�JN��W����2]����N�L����
���B�sg3�=f�q��N9ZO�"oMZ4�	j�/���?�S~�1�:���%�8������L$��������\�����V��1G��&r��"O��|�����_o8�v������aF�q�1G�]��*���G#�=q9���}��������wgG��{�}�;>b�4D�[�p�����N�$�v�7W'��9� $�����Ku���?7a���b�N55�O�Z7<k
���,�8D�v����7�-��$&����.�Z������������-�(�f�6�
�C��'��"�"�J�q��������?��#�%!u��	�EKt�_��:����T����/��Z�x�-�s)'A?�	�h�����I(�Rj�h~������v����R�e~�I�w8���P��R+$���C�W��������|��[���g��&YrqB"v4���g���1��p{>��T���3���h�������Y�x���{��p��g��P�������+��D�=��-�({�iC��b��/`���`���S��'��=f���l������2R���r��W�f��m	?�|���.�������>���	�:<q(��T��u'�sG���G�p@�G�a���E�$4��qW�^(~��^���l�������W3���~��q��+p��4���F�F�O:d�qy�S���J��>h�w�~������<q��t�\��7��Z!�;A�S��}]�k�4#OHUN%�����x
J3�vVs~���>����7�?�x�LM��h��[Z�&CY��v���o������)h�h#�$�n�M��wd�s�~|�������j����-sq�})�����IJ�X)��a�;��)���p8�u2E��l����|!��Ez27�d2�l}��p)i�7rp��Q �G��6}Or��1��f5&�R�)�$�tx����������j��r��!}y4����X�x�������3�i���e=�Gy!4>��p�RK)o���B��|;
]�D���<�~R��h��D#��6";9�����|
����u��4�K��c
�>P��DC�m�Jn&/��7�G�y��?���

��s������666��~
sLQ\�?�]hlh����JT��N�����p)2��t�a()9��C�(��.��?�4JO�j8t5��8*x�Iu?�R7�T@7�-��
����)��0�e�E���	���b�������AC��H���d��lR�.��S.DrC��~�m���������q����X(��4k�:A�W�~�9'-��Bc�I�6O����(T*�e~>�&���6���O��[]3H[|���i�5]~l'?78�,O�r�Wimm���W�4��q����l�0�@�~�Y����F��"0t�{-�^����+�����
��"�d��uVHA��LSS����O�I��bc����[��".���^�3B�������?T���vC����mL��k8����Qo^L��j��a<�_����������_)�0�qI�vh�I��]d#���;*re/����N�tj�=�>�fs�T�~?��4��\�g� !�|{�Hs��a�6�Y��"��VK���G,��?�5?X���?����z��7(�TE<7�N����T��A�|�H����7����
;7PJ��;j�#t���!�K�J�#��*�+�,���+��J���y�
4���b���+U�B����`�o��Tj�Y4�]���p"*���K�?x)=��O�,V���^��R����99x��L���e�a9���&��"���H�rD^���_G��������*pm�C����.<zZ��$���.�
=�����jw���V�����C~P5uu��1�So�XNq����V���$Dd�����^���KLx�arE3���E��������c�����mv2��f�[b���-H�v�&�.�����#�8�Yb�e�$:���(�9������1�P��N�2�dR3���8�nB�
P�!�B�A +����W��5&���q���j�n8U�_x�0h������&�Q.f!��3�Q��4���"Gj%)kFe���
�~�L�oF��l�:-���MJ��D��.8�'�@T-�Lp}g�nNpUt����9�ZU|ii�R`R�_:�� _4�����vt�T����~���2���3��[1�4[�|���u2���@B�U�|+����a���+�1�R���an� �-��n�+��5�M��������E���Iv�}�{+w$;�N�U6���3k��8�1�r�&rs/V�(g��Hh)��R���j���B�F$��E�'B�eR�����\m�,>Ms������Sg�e�L������Il�7��L��u���t��I#Y&���$���������n��������.M�<�A\�s"��@�_��;Z2CA�S�-^egh�AW�k����z:
'����{��q�Z��(t�n�%�C 	��C[J����iJ��*.�	E�;��nv���6�M�Z*9��C�����h����A2���qf� +�T���d}��Q�G��
f2a��W|�K�=d��y�������A�n�vO)��#.%\�b��F���SR�]7w}��b�uB���`o}\�DuB�6�����
(5i��<1�UZ��������� 5�l���������h6�m3I�[�n�7{�+��m�9���d�F��(�H���5#���`&=���h]�E��3}w���t����j���0��L��8��4"�d]D�}`�_�	?�]E��4E�}�&�>Q0<lIg82��z���G���7�\R����I�f�,�0%~3]E~������/�O�����e���	��h�Ma�It&.�5U����I�
l����#k�g��h�:�N��e��(�;�s��I�d�����}9|�����Z�T��J�Ta�;��T������r7��J�^)���)�Uu��79
�7+�+�f�4����+�NCX���&��4V'��t�bce���������55�Z.�#��>�{��O�{|�/���G�����l��t;oO;gW��$�e�M<�'�l������'*tN:\a6�"�W"����V-!'����1�i��M��=����nJxT�F�������x����!]������2�����X�|@�fXbN���$����i�P�%�+���q0�<�Z����W�����w�p4T�B{Y?N}d���9Y�x���*��1>�J���������\� k����
$	0�������L�R�|�~�9���7�2d��������Nk-������^q�hM7?pV1&��������KO��%�4r)�Zc����
C��"N k~�
'Ek_�b�(�����{_�C�JZ�a����B���Yr�X��SAv����v�Q�G|f?������=��������(��e��/D{��'��q�B4��2f.������
�����'�o�L����{W������E��H�)WI1S�*��Y4�^>w�� ������m��9p�Z�����W����������%��CI�4�B[���O���Pa�>z
"����R���������JoC�"���uMnAhx�w>~���o�S�>�UG��.�8���Y����������j�������r�����M��|O��k�/�m��R�k%������X(���y��R���;�����T<��N3l��O��X��~6f�w?��\-��,K�6N���-��B��Sc�]�B�lj�I�q25��v�~#x���y���y2�*g�(C���$��8�OA����1hc����w�+'�:����,��� ����dtr��u�W����]���:>�t�9����.Ix�Q�D�e�%)U|�%)U������%���kTW���5E��J��R>��~��)B�����tb��6�x��p��q�����_��e�G����W�0�%�5aYv�mk`~����z���$����
 A���Wa��Lpt����0�&�G$���V�=uxO�Vmb	`��"�����FXI�Qh������H4|���g���^��S����l�4}�1�14)&���D��tG��@�++B���>�	���~�!"��Aq�_�����.�x[�rj�T�^��W���������u�Z����5vW��
�f����
lA�7���{�B������uPm����0���;��<Ly(����0�"6~�1DJ_��L�h�f?���W�X|����y������x�������S�Y���+zd�6����	�,�d�Bi������"&i�fBj�a�d�F��;t�y<��~0��21t(�����HE��7  �� ��/�o�X�O�
p%���;���vO���CL�G7w���`o��p��s��R,���-l��.<h�!����	�(��4��2a#.�6B�\6g������%���0�������o!M��n�-����k)��P�g��P�a��/I�yqoLngw�2��'>��B4^L��G���)�A(A��(tG������^-Q���Y�'�A�����qHQ��|��8��(��]6��o����{��@��x������uN/N:���qUJ?��7�'�=��
z\�:�C�8�%���$���f�����n���hF��q��{o{���K�����^\��/������������������p�J��_|,�h�������p�3�v��]��������|/���Zmd���{�-���@�g ]�������P,q/;��W*���M���@���:�g����-\W.:GBz���Op���iq��`�y��h�!�7���������1w��b��7V��Y���0��Z���q=!6v&���f����KI��
�&7�[�,�(g��x+����b`���#-��)�������_(����s�U����G�2y��,��C )X�����S��Ee�X�����*��r�,
e��`�(43�1�(�a��"+�q5�������q����\\\�_J��"Vj�:��G��F��6�A ��|A?��=D�t"�L
����"i�(��3��T�\���{�h�'[,
[�t��M5k���7�/��a���OXD���w_����K/�sO�ww��n�� ��4�Q�j�mr�UF������J�.�t�c�� �?�=�_��Lp���G3���W��U���\�����f�r�
�;�W�w��Z���&��U�������Y��[����W�v�?�_��-r�|<�����V��[mLN��V���?��[��_���3^�=]A
���
JlJ1�� 
�X�M%������I��6 m!��z8c���|�9�����?|<����q���$�]z��]ns�gA�4�;���A���Y���q��E�?�%�0:�W�����(�y~��u����_��s3=K�=��Y\�W����%/y{�_��B����SC)�6��?[�Ck+*����������?����x�k�����o��&�$	)B8�o��l'�������E����9�����'�,	(���w�)��K]\[bbP(;ad������R���;a��J��}$�[Ez��iO���7�Kz�0	H����H�����dOX��iA�;J�y��(�H��/J�`�}�K�
��aIS���n���+�����C�egSS�)k&xw.���� ��hf�4������IH���\vlrlh�,`��� Z<���&B��l&��-�
3�p*�|����&
m���s���L���,Pv��j1&
�j����G���|Z,q|7B�T�`�f�����������\��h9�~\����}w��"����k�+���&���)��:gWW�?v��<��4���f)��K�����!��{����xv~u|���M��1j�!�@<0ZO�h��N�m��4���>:��a����E�vSF���B����S'�`��r3��p�E�oC�b�����UU���1�]y��y~��K��j�&
��y�-
Z�)�[L�)}���vN�F�I�Ji�]p��z4�e���s���1�����w5{�ZU�u��+Dn����9T|�k��7>�����+���<�:;���*u�Q0��:�;
��~��,���)�7�8�_�}1���g��?c�p��X8o�������3���>�I�w�(P����}�;�R�:I4@��e�Qz��������(�[sg���j%OR�u]����d0�C4���K�7�C~�q��.�2�s�} qG��0��E�����(-H���Z�o����SQ�V�F��H���$1���z��@�EP��������u�v�5�G]�l1�c�J����Gh������j�O;�p�~�Q1�T'��M�B�!��-��N44�����U�,���5�j�����|@�;����<�o��c�V�y�E�8=���G��������^m�Q^O�&��j�
�%�T>A��(�a�}��>lY1w�-���oA�Zg�����	TR���~s�!�t\T���2�k�����e.�c��������`�����q�W�/��
Df�d���YT�&��GSZ�����Q� �4&<h�+F��
������R?��"��u��3�-8���hb��_�s�L�WP��>����p�_�����%E;G+7�~��cn��(|WG�����Q�sb�����@a����T���(��������������G���2�
�|y�B�p�����u��/������2
I8�?=	�;G�qnK�������\�>�{n�{�H��
����DDl���s�"4{I������4M��H����p��<P�3�}D�|�����G����|Z�b��/���4�w	���`�W�#���]�K��!T���\"0e�H��@`s��sX,���T�T���Vc����G@�h�gp�I��:��G��cc�,#z�,������;�����0��pdE�� �]v�N;+�F\t��>� ����n��mJ���$4��'������7i� [VF'�8@�VO����?�a,s_�p��'�t��c�������]���������w*1��0��s8��IM�G����Q��@��JP1�pB���s�oxm��!r�� ����B�0���o`���x��X�g�pN�
]��)5f�_�>��/?�����|�|����eY�D�w�>������5�F�Q��� �V�e�[������H��@����t��N�H��_�-K����������/��[=����� ]�"� uc�@�)Y�8����"�3�������h<$���%�\hFN3�Q�$j�L>�E0� v
��o����{M�g���A\2������J�2����$CB���!,H3!<��1�	TB�y��������-�
%�Y1t�"�)����`���ix����H9q���u����!�����9��=�v�0�<]��OO��^^����>8�aj9E���8��":���@�q��>���-?�XL}:���Y4���)<�����_��)1%Z�E��S8C�dNf�>,+\q����F��BCWz���Aq�,�lw"+�����JG
��6�I��G��t7������s�Z����9���t|ji S�����y�t����x�%6���hN;��F�c:��0��Tc	1�
�{����Y��D�P87/4g$��s�}���&��/���< �z�����O�`4���6,���e�������cRN��|\r��_m������D����"�%y��{�o��e��$���<���J��W�fpk�	��V�����?������&���7������������U�(&�YNd�G!m��w�ry�.���Gc,��Q8$e[���?���}O(\o���q���T����	�{%$�v���b�W����:=���s�{s~�{wqtp��A�oV�j�&�?�j��K��-I�^����i�+����Y5�������C�����t�k_�F��7��.�v�o�6�}��9Z���2�+��m�Uk]����"E�W
�����;p%��I������x�z�o5�5��a�>��d���|=���B�{!""Rd5��f'���H�H8��$���)e����������k�
A�q���=��a���x��&�9�8bv�n;[.w��]E`��l(�C����� *5�]��FU?���X��������n5��������zwq�c��%�(\\����t��<�},�G��+.�5��������<�}[9���x���SIU(����]�m����\?�5l��3���i���]{�h��-*�a�>��0����$0�p^�z\,;���KSv�u�����g:3����pB(��k�������`�E������j{����������Q��7VK����+��	0����0��So{��G���������0�~�_
77�������|V����58��������4'�,���m������7��A�R-�)(#N��~M�I�B��M��\4X*<tq��Wv
�Re����L��=��wW'�gs��1��7�?'0���KN�cd�@�Gq3���"���)|��2��z�I�+`��|O��}��/���	/��[J����Qi�4���G��vL>�3��x��@���F3��N��������|H�&��1���_<:P�d
+������i��_�W� �?_V����(�6?Ky}G���6�����~|������I��-���/��?�U��i�l
K�����U�������6M��N�W})t�8���,3sK�Og!��*��:���+'L^�����JS)�ux�E�$�u����TAB�������F������qd�)�����HF������
3��]]�u1���Lx���L�P�cYX�
SW�#*\Y\����%��U i�)�[
���\��)h��H8�OnQ!\�a�9��t�`A�<��O"�U�F�hJ���F	�oQi���w������������\vm���Z�r�d$��6a�*�h���������������Xe�A3���^,Bc���X���l��k�q$���'���
LQH��Lhd������~�L~�����9	�����c��U<�`�
G-�R���x�P�k���g�����������X4�c~y[/�Q��It{.f�7J&�'�F��
}�Qy>yBe���������m����f�@���2�OhC��h�i|��m���U���k�2�F.1E�Z�x����$Z<�'��a2K��
��.��������4�����!��A����7do��W������G�Z�����m�����rb�����=�uG)�����3ff�o���\3����"r]��u�@'ym�������c������1M}�5�����.����|�77 ��g���d>����t)�.~@���T����5�R)�w�������"�;G��3�JC�|�p�%��=����{�_�.zR����V.�x)%�a�j"�c����oG��=|d�s
���~�=�h�����<"	P.�aKKIlI!AbT��$���r$Sd�<�G�	�=���{p0���Nw��t��b���
�4��:RS���X1���������������	��0����p��4'��n���=>b�	/���G�mR)�4����7p��\������O��A��f�3f4��`���N�� ����W��/4z�X��O����7���ra��e�&��$��,������l����t;��:gG��Q.bW���A���G��_��`��~�w�\)x�'>Gdo��U����k�I`a�K��M��t�(\�	�:��nN������S���4J�,�2e��j6f��fByw��[9m�
�V�k))�)�aN'����o�P����\SW�2����~
���l�m>��V,*./���e��g�y�W*~��4��r�"�G.��@�Ez�od�.y��U|pa������_*���(s��-�����wu�}�J��B5�fY8��?Qe��s�<k�����h�TJ�I���^e�K|���_s���������"�ZQt���Ymz��?��@@��(w�
���S\F�g���p>��8����c���
������9�����g:c`Pk��QM�_���������i�
[��8T�����>� �c�Ps'���� ���D�W'��9���(��7��1��A�&�=�)�2(~�V��L0�.��(tc����OQ^����C��1�#�����Q���:w3�P��w������|)�x����|o�[�3��0�/a*��`��a��PS�L*�
R�&�+f���Yy:�C�!�P1�u� �2��
�5������`�cCP2���t ��0k8�Ql\0(�.��R�.Z�4��t
�Zs�,We��l�	,�OK�����.bS������!�G��D���_�
�
��[��8���3����e���V7���n��V�/�x�����
�z.��V���M�d5�8�hU�3zy�CKFO���7�W�M�~�=}{)����Ka��75���h-���W���|���w�WUi3��O��@�'����@�J��8V�S.��P���Q���#H�V�+����8-M��*v�gYK�N#Z�]VJ(B���5�1������]|�>�]^����1$z��Ox��T1j����d�W G.������q�P��G�b��R�����]�x�i�R�)`���)������j��\[~���]
�?��o�]��p8y��D%���������jW���������1�o-4����w���@�(����.�vsG������k����v1��g���0�����.�q�`������c�uU�ue��������<3���	u��$3F��{.8����~]�D�o�dLa��a4}�y������Uvw���r�7�zK���&����=�6���eM�}�2�5���M��p�A�FL����o�����v�X��*������grY�u[�kg���Q��;�
*����tAW����w���~/�|���J��`�`^M���
�\��i��<�J�7�0f��s���n����*F����^G����,��&��/+t7�
����R��gq9v�Qe��l9�|rjZ�>)F-v�"w�#&sV��4���+�gUr	{V��M@O�,������+��Pn���n�E�6�;�4e�4�rxyoYh~|��>����[��^���BS���r<��7�h�?cK����$hAe�z��'�)�N6�|��Ypo�}��3{i��pX��y�QD����'U
*
�`���
D^����?*�o�WWw�'5��@V;��l�&��xqC��+�"�����c����f����yNa��6?��2XO.�h�1�'�����,���\HiF��)��W�<J3b5GQ� �[{��D�f�o�����'��?���d��o<���R�E�����6W����@��>��x��A_8��� ���=>��2m��������U]��w�C�������gT�o����R�q���������p�K����b��97.�_-��l��D���g��&t�?	�Y�#���c���u�/&�`�T%�-M?���\7�p�N�x��9=���O�v;������Z$TB�77f�:3i�9B��|���r�K%	������H�5�����"�[��	���0A6j.���p�a������!����%�^�9TX}'r����������� c��{F���\���1��G
�&�o�s��N�������I�;������;��<�_��Ug��z��T<�i8����
��X((D���n�vM������U�~EG�=�$1�O0�@xkN���l"y��pBd����������G���������.��5���V�W	���K,["��S��3w���M���E3J����P����\�X��N�G��BA�2���t���)�F4`:\�K���Xs���k�Xp<:�UOm<��v_f��e����
�z���9N�GNFD�8��(��%���l�Z��E�P�2���h4�(��?����S��XDW��$,������.M�V+j��=n=+����FsJ3��������Li��s+7��JydG�.�#����:.-���U���K+�=�E���y�GCQTG|��KQ�5����ZlR�w�����.�H�#��ns���L
n����M�#>��g|r�W��5i�/���h��j������� �����J�IW�~(SIgZ���K�pd��j����"�������&�:Syl��c�)�5*~�az����n�p(7'�}TR�n�)��V:$�y��^���b�415J�+}���yrw�F2�{��3��<�3|�!0Bf1��hj��}~�~Ux�'/g��?���J�i[]e�����e���������Z}D�^�p���>�h"n�=�%���H���Jq \��#�p�Ef�%�	��B��+���Qw��k4b���4��0��S7��
��h[�]N����]
r�T����]Vs*@~h�
��,�<�����@o��zEw�I�bJID=dE���V���W\�99*i����[B�R�0�%��m�ye��|&C������>ws��{/��Vj��g��� BcB������}����uv���������7����"J����>P
-���j�Gh$���6�6��FC`�����{�C�^�i}(N��S�yodZ��$�'j������2���
��������rW����L�6��$���J�A���@�^�a�B��K�&n����;��K��n	��8�F�''��g/c�;�T�3�J�)��5V�������-�4��+W����Y��d�
<�����(~�L�E��].���K=Z���c�)v�����ug��qo0��(F��$��E~<�Q�T��An������>����Z��4?�����*�+��E��c,J��W�=~��F�7u������Cyt���eb76��BOU���5���`8����h2	eE�A�9��x���V�Q�(MRF~�dM42�z~�$5]2�\��t��gAF/W�����Gl�����A#�8��{����f#t�<���ri)�,�d�
� 5�����9'����}Qt��b����\���a�z��#����|+C�o�ub���/O�ySal���v��2��R����UZ{��^��8���{c#�b#��o ��}e��y��8���|=��������w�wF�q@'�<h&��	�I@������CE
I����_��]r���0Hh��/)�o]��iLg��O
�1�W�e�|��
�����p5�� ������|��F>�hEK?a�S��|�B�
/��L��iF�v��5�Sc�����.p���xhd��P����h���]Vk�~��pY������s�����
~���!�=������|<��
�� E��u��hdt��'aq}U�������
5P�\�Y2���d����Yz3
hl�G��� �zE�+�Z��$�����-�c7�3����
K�9��K��w����������_��<������is�eG��*5kkF�:�{�f���w]�z����;���){����S�&)A��-�`���x� KQ����.sy�B����q���5�:�������>M&���`��]d�q7<m�;�W6mO�����>u��4����,�l��r�+I�q^��j�RiW���D��_���v�O*�8�7�|����D��0����m���ek�<}1��I(�D��02QD|`��A6�����eW@��
qcT{��Nq	�;4ux��0 �(��P
�%E��0�~��!4���<
�����3�JsS�w�xv�S�,��^sS�(	��P�cT<U��c�����d784��]������]��lWT�r�6��9�pM��>�������*	
��b�����%�Q��.�O���'B��#�{���(�"���=��D��<��T���B��o =?�(��(�
�����k5�`)���A�_���hw����ih�[4��0�������97��$v'2N�o*�F�o*�2nzX(�#���(���#�$Pz��N�?���m�k��?x������"��I�i��I2�IQi�h��B0aL�u��0B��'��Og��R�����vJV�TZ"E
��I4#�>�,�/�>7���!��� ����"B�
B�#���[S���6���p2�� ��+�5,]�!��3^d&8��V����P?L�{����p�OY1��J���Y��;
��6���
3�F�L��t�	��I�?Mn�v��\AX������.�����s^q�Zbo�To�&��P���f���b��="j��\��+6��'���&�R���_���k1���-sv$(��B[�^w��1;����W�������v��/���~z���Jz���������d%Uz��H���45z����$��������`<������x�����=��M���Y�(Jy,/V�c2���5����%���r��Y'o�
���e����sP��We�=13���$�&d)#,"�t�<�L�{�$����A8Y[x�����gu(+Uy��K#W������{?�h��7���J7����8����RQz���� }�Y��c�yr$�oS���vM�n�+r}�cs��A���sY�
��J����L�I�bU��)��xdy�L9tHr�:����U�6�����!xH��Tj�~�n�/�W�J��:iG#���B�����
��q �c[	��t�_*X�
�x�+"U�9Mh�~b�$�
�z z���	�p���2z���J��Zyt��������	����o?��,�$3���%�[�B����[Uc��n{�[�z/Ky/Py�����GG�d��!G���~�@��&���P�������`��B��,T��F�lcp��Qt����s�]���A��=M�u��;�@�"�{�b��l�v>��=nwR�aE�������������&��]ZVh:C���F("���T�d���rj
oP�����^��9;J��}��i�8���h����I��n4����������4U�����=���n�bO����}�{<SQ���0�	��G��]��`���H�"a��y�u�P���t5�(1���iy���0Q�&?�v��>��g%�l��4g[;�!�-��{<C�i����Y<������u��7�l{�� Y�9��R&C2�"^�����m�p���kh��0���!�IDn&Y��<a9-T��RO���D���I�o�l�b��s�������t��t���W��$�rf���og�k��#L2AU���xu��7�?c�oF���	��w��������d�f'�i���=��H���#���-��`�f~"3���4���b�$	��A��,~�9���h[|�f��v��~��^�QB���2J����]ML�����U��#�Ja���r��sE[j�<��B�BIv�NtF!w&�U��������P��_�o�I� ����D�e9����)P��z��!W:Z\Win��C�3�[qtrr|zq~y��N������Jb�W�pM��f���w6��D���3��U�1[1D����R����O�R�V_NQ��Yy5�>NQ2��O#D�hXN���b"���KN����C-8y��4\�����`�$����iI�C�K���3EY�j!���Wh���U�������MJ�����������;}���Yp/ZtN�;���Z��;q=���F����K.�p]�0�4����j�1l�������2����������:��$������H�v��
�E����.#�8U~z��"�����p4�tw7�C2P��ii%2Z���OP4{LH\��T�lW�@#��q���zK0���.=2� ���`�]��.������mZ��!���:]{��*;�B���E�5��2�`����V{�{?�H���H�>4j��AV�n(&��^v���MV�����b�([�J�T�.]b�CX�����OS��8,	L3�!�����w�ZH�5:��^-�+�����8�����m3�[���+��!.����#����+��9�<�`\
�Qb_���}�����<
�tmk
d�$P~��K0)x�0�$a���n�
������?���t.��W��]�����-�������D�5������6M���f��.V0F���U7������	jt�
�*����g�@a�?�]x�j��u��|
���(�S�@�4�'��+�>o.�OiX���wx�x:#���GMb��b����D���M`/�&0�0}
b���=�6���7�����2
B�����?0(��;�������]q|vuN������N�+V2#J��DDv�U�#��R�#��b��Q��5w0���`�`u#�^-E�����R|�RK�U�|Z�.��qp����lG4����l2������(������`	�wUYx�*<��������F�7����J�����O���g��e�sc�����9�}e��x���9�Y�
#24Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Alvaro Herrera (#23)
Re: [PATCHES] nested xacts and phantom Xids

On Sat, Jun 26, 2004 at 12:42:28AM -0400, Alvaro Herrera wrote:

- discussion whether we want a different syntax for subxacts, like
SUBBEGIN/SUBCOMMIT/SUBABORT instead of BEGIN/COMMIT/ABORT. Please
comment on this point.

It has been suggested a couple of times that we should use a different
syntax for subtransactions than for main transactions. This would for
example allow things like

BEGIN;
do something;
SUBBEGIN;
do something else;
SUBBEGIN;
do more things;
...
COMMIT;

And issue a single COMMIT (or ROLLBACK) to get rid of the whole thing.
(This is suspiciously similar to SAVEPOINTs). Another nice idea would
be to be able to name subtransactions and rollback to a name, which
would bring savepoints even nearer. Also maybe a subcommit <name> would
commit everything within that name (not sure if this is a good idea).

Please comment.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
Este mail se entrega garantizadamente 100% libre de sarcasmo.

#25Dennis Bjorklund
db@zigo.dhs.org
In reply to: Alvaro Herrera (#24)
Re: [PATCHES] nested xacts and phantom Xids

On Sat, 26 Jun 2004, Alvaro Herrera wrote:

(This is suspiciously similar to SAVEPOINTs). Another nice idea would
be to be able to name subtransactions and rollback to a name, which
would bring savepoints even nearer.

Sounds exactly like savepoints. What is the difference and why don't we do
savepoints instead?

--
/Dennis Bj�rklund

#26Greg Stark
gsstark@mit.edu
In reply to: Alvaro Herrera (#24)
Re: [PATCHES] nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

It has been suggested a couple of times that we should use a different
syntax for subtransactions than for main transactions. This would for
example allow things like

BEGIN;
do something;
SUBBEGIN;

It might be awkward for clients like psql that will want to execute every
command in a nested transaction. It would mean they would have to know whether
the user has started a transaction or not in order to know whether to use
"BEGIN" or "SUBBEGIN". But I guess they would have to do that anyways unless
there's some protocol level way to indicate a query should be executed in a
nested transaction.

--
greg

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#23)
Re: nested xacts and phantom Xids

Do we really need SubtransCutoffXid? AFAICS the reason for having it is
only this claim in RecordTransactionCommit:

* We can't mark committed subtransactions as fully committed,
* because concurrent transactions would see them as committed
* and not as in-progress. Leave them as "subcommitted" until
* the parent transaction is below OldestXmin, per VACUUM.

but I think this is dead wrong. As long as we mark the parent committed
first, there is no race condition. tqual tests that are using snapshots
will need to recognize that the subtransaction is a member of one of the
snapshotted main XIDs, and those that are not will think committed is
committed. So I want to mark subtransactions fully committed in
RecordTransactionCommit, and lose SubtransCutoffXid. Comments?

BTW, it would help to know what parts of the patch you intend to work on
over the next couple of days. I'm reviewing and editorializing right
now with intent to commit soon, so it would be good if we can avoid
tromping on each others' feet.

regards, tom lane

#28Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#27)
Re: nested xacts and phantom Xids

On Sat, Jun 26, 2004 at 07:56:09PM -0400, Tom Lane wrote:

Do we really need SubtransCutoffXid? AFAICS the reason for having it is
only this claim in RecordTransactionCommit:

* We can't mark committed subtransactions as fully committed,
* because concurrent transactions would see them as committed
* and not as in-progress. Leave them as "subcommitted" until
* the parent transaction is below OldestXmin, per VACUUM.

Right, that's the only point where it's used. I don't know clearly if
some kind of mechanism will be needed to handle SUBTRANS COMMIT states
in pg_clog that were left behind by a crashed subtransaction though.

but I think this is dead wrong. As long as we mark the parent committed
first, there is no race condition. tqual tests that are using snapshots
will need to recognize that the subtransaction is a member of one of the
snapshotted main XIDs, and those that are not will think committed is
committed. So I want to mark subtransactions fully committed in
RecordTransactionCommit, and lose SubtransCutoffXid. Comments?

Yes, sounds good.

BTW, it would help to know what parts of the patch you intend to work on
over the next couple of days. I'm reviewing and editorializing right
now with intent to commit soon, so it would be good if we can avoid
tromping on each others' feet.

This is really excellent news.

I'll work on adding the Xid-cache to PGPROC and using that in
TransactionIdIsInProgress and the tqual routines. If you want to work
on that let me know and I'll handle things like the password file, local
bufmgr refcount, etc.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
FOO MANE PADME HUM

#29Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#28)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

I'll work on adding the Xid-cache to PGPROC and using that in
TransactionIdIsInProgress and the tqual routines. If you want to work
on that let me know and I'll handle things like the password file, local
bufmgr refcount, etc.

Either one is okay, though doing the latter (ie modules you didn't touch
yet) would be probably a bit easier to merge.

regards, tom lane

#30Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#27)
1 attachment(s)
Re: nested xacts and phantom Xids

On Sat, Jun 26, 2004 at 07:56:09PM -0400, Tom Lane wrote:

BTW, it would help to know what parts of the patch you intend to work on
over the next couple of days. I'm reviewing and editorializing right
now with intent to commit soon, so it would be good if we can avoid
tromping on each others' feet.

Oops, I forgot that I had inadvertently left a kludge in
commands/trigger.c. Please use this patch for this file instead.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
Jude: I wish humans laid eggs
Ringlord: Why would you want humans to lay eggs?
Jude: So I can eat them

Attachments:

trigger.patchtext/plain; charset=us-asciiDownload
Index: include/commands/trigger.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/include/commands/trigger.h,v
retrieving revision 1.45
diff -c -r1.45 trigger.h
*** include/commands/trigger.h	29 Nov 2003 22:40:59 -0000	1.45
--- include/commands/trigger.h	25 Jun 2004 22:59:40 -0000
***************
*** 151,198 ****
  					 ItemPointer tupleid,
  					 HeapTuple newtuple);
  
- 
- /*
-  * Deferred trigger stuff
-  */
- typedef struct DeferredTriggerStatusData
- {
- 	Oid			dts_tgoid;
- 	bool		dts_tgisdeferred;
- } DeferredTriggerStatusData;
- 
- typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
- 
- typedef struct DeferredTriggerEventItem
- {
- 	Oid			dti_tgoid;
- 	int32		dti_state;
- } DeferredTriggerEventItem;
- 
- typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
- 
- typedef struct DeferredTriggerEventData
- {
- 	DeferredTriggerEvent dte_next;		/* list link */
- 	int32		dte_event;
- 	Oid			dte_relid;
- 	ItemPointerData dte_oldctid;
- 	ItemPointerData dte_newctid;
- 	int32		dte_n_items;
- 	/* dte_item is actually a variable-size array, of length dte_n_items */
- 	DeferredTriggerEventItem dte_item[1];
- } DeferredTriggerEventData;
- 
- 
  extern void DeferredTriggerInit(void);
  extern void DeferredTriggerBeginXact(void);
  extern void DeferredTriggerEndQuery(void);
  extern void DeferredTriggerEndXact(void);
  extern void DeferredTriggerAbortXact(void);
! 
  extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt);
  
- 
  /*
   * in utils/adt/ri_triggers.c
   */
--- 151,165 ----
  					 ItemPointer tupleid,
  					 HeapTuple newtuple);
  
  extern void DeferredTriggerInit(void);
  extern void DeferredTriggerBeginXact(void);
  extern void DeferredTriggerEndQuery(void);
  extern void DeferredTriggerEndXact(void);
  extern void DeferredTriggerAbortXact(void);
! extern void DeferredTriggerBeginSubXact(void);
! extern void DeferredTriggerEndSubXact(bool isCommit);
  extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt);
  
  /*
   * in utils/adt/ri_triggers.c
   */
Index: backend/commands/trigger.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/backend/commands/trigger.c,v
retrieving revision 1.165
diff -c -r1.165 trigger.c
*** backend/commands/trigger.c	26 May 2004 04:41:12 -0000	1.165
--- backend/commands/trigger.c	26 Jun 2004 18:12:01 -0000
***************
*** 50,58 ****
  					MemoryContext per_tuple_context);
  static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  				   bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
- static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
- 					Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
- 					   MemoryContext per_tuple_context);
  
  
  /*
--- 50,55 ----
***************
*** 1639,1685 ****
  
  /* ----------
   * Deferred trigger stuff
   * ----------
   */
  
! typedef struct DeferredTriggersData
  {
! 	/* Internal data is held in a per-transaction memory context */
! 	MemoryContext deftrig_cxt;
! 	/* ALL DEFERRED or ALL IMMEDIATE */
! 	bool		deftrig_all_isset;
! 	bool		deftrig_all_isdeferred;
! 	/* Per trigger state */
! 	List	   *deftrig_trigstates;
! 	/* List of pending deferred triggers. Previous comment below */
! 	DeferredTriggerEvent deftrig_events;
! 	DeferredTriggerEvent deftrig_events_imm;
! 	DeferredTriggerEvent deftrig_event_tail;
! } DeferredTriggersData;
  
! /* ----------
!  * deftrig_events, deftrig_event_tail:
!  * The list of pending deferred trigger events during the current transaction.
   *
!  * deftrig_events is the head, deftrig_event_tail is the last entry.
!  * Because this can grow pretty large, we don't use separate List nodes,
!  * but instead thread the list through the dte_next fields of the member
!  * nodes.  Saves just a few bytes per entry, but that adds up.
!  *
!  * deftrig_events_imm holds the tail pointer as of the last
!  * deferredTriggerInvokeEvents call; we can use this to avoid rescanning
!  * entries unnecessarily.  It is NULL if deferredTriggerInvokeEvents
!  * hasn't run since the last state change.
   *
!  * XXX Need to be able to shove this data out to a file if it grows too
!  *	   large...
!  * ----------
   */
  
  typedef DeferredTriggersData *DeferredTriggers;
  
  static DeferredTriggers deferredTriggers;
  
  /* ----------
   * deferredTriggerCheckState()
   *
--- 1636,1763 ----
  
  /* ----------
   * Deferred trigger stuff
+  *
+  * The DeferredTriggersData struct holds data about pending deferred
+  * trigger events during the current transaction tree, with the
+  * following fields:
+  *
+  * memcxt is a private memory context that holds most data in this
+  * struct, except the events themselves (those will be saved in
+  * CommitContext) and the state which has its own context.
+  *
+  * state keeps track of the deferred state of each trigger
+  * (including the global state)
+  *
+  * events is the head of the list of events.
+  *
+  * tail_thisxact points to the tail of the list, for the current
+  * transaction (whether main transaction or subtransaction).  We always
+  * append to the list using this pointer.
+  *
+  * events_imm points to the last element scanned by the last
+  * deferredTriggerInvokeEvents call.  We can use this to avoid rescanning
+  * innecessarily; if it's NULL, the scan should start at the head of the
+  * list.  It's name comes from the fact that it's set to the last event fired
+  * by the last call to immediate triggers.
+  *
+  * tail_stack and imm_stack are stacks of pointer, which hold the pointers
+  * to the tail and the "immediate" events as the start of a subtransaction.
+  * We use to revert them when aborting the subtransaction.
+  *
+  * numpushed and numalloc keep control of allocation and storage in the
+  * stacks.
+  *
+  * XXX We need to be able to save this data in a file if it grows too
+  * large.
   * ----------
   */
  
! /* Per-item data */
! typedef struct DeferredTriggerEventItem
  {
! 	Oid			dti_tgoid;
! 	TransactionId dti_done_xid;
! 	int32		dti_state;
! } DeferredTriggerEventItem;
! 
! typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
! 
! /* Per-event data */
! typedef struct DeferredTriggerEventData
! {
! 	DeferredTriggerEvent dte_next;		/* list link */
! 	int32		dte_event;
! 	Oid			dte_relid;
! 	TransactionId dte_done_xid;
! 	ItemPointerData dte_oldctid;
! 	ItemPointerData dte_newctid;
! 	int32		dte_n_items;
! 	/* dte_item is actually a variable-size array, of length dte_n_items */
! 	DeferredTriggerEventItem dte_item[1];
! } DeferredTriggerEventData;
! 
! /* Per-trigger status data */
! typedef struct DeferredTriggerStatusData
! {
! 	Oid			dts_tgoid;
! 	bool		dts_tgisdeferred;
! } DeferredTriggerStatusData;
  
! typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
! 
! 
! /*
!  * List of per-trigger status data.
!  * We give it its own memory context so it can be freed easily.
   *
!  * saved is a boolean to flag whether it has been saved by the
!  * current subtransaction.
   *
!  * all_isset and all_isdeferred are used to keep track
!  * of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}.
!  *
!  * trigstates is a list of DeferredTriggerStatus structs, which holds
!  * the per-trigger state.
   */
+ typedef struct DeferredTriggerStateData
+ {
+ 	MemoryContext memcxt;
+ 	bool	saved;
+ 	bool	all_isset;
+ 	bool	all_isdeferred;
+ 	List   *trigstates;
+ } DeferredTriggerStateData;
+ 
+ typedef DeferredTriggerStateData *DeferredTriggerState;
+ 
+ /* Per-transaction data */
+ typedef struct DeferredTriggersData
+ {
+ 	MemoryContext			memcxt;
+ 	DeferredTriggerState	state;
+ 	DeferredTriggerEvent	events;
+ 	DeferredTriggerEvent	tail_thisxact;
+ 	DeferredTriggerEvent	events_imm;
+ 	DeferredTriggerEvent   *tail_stack;
+ 	DeferredTriggerEvent   *imm_stack;
+ 	int						numpushed;
+ 	int						numalloc;
+ } DeferredTriggersData;
  
  typedef DeferredTriggersData *DeferredTriggers;
  
  static DeferredTriggers deferredTriggers;
  
+ static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+ 					Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
+ 					   MemoryContext per_tuple_context);
+ 
+ static DeferredTriggerState
+ DeferredTriggerStateCopy(DeferredTriggerState state);
+ 
+ static DeferredTriggerState
+ DeferredTriggerStateCreate(void);
+ 
  /* ----------
   * deferredTriggerCheckState()
   *
***************
*** 1704,1710 ****
  	/*
  	 * Lookup if we know an individual state for this trigger
  	 */
! 	foreach(sl, deferredTriggers->deftrig_trigstates)
  	{
  		trigstate = (DeferredTriggerStatus) lfirst(sl);
  		if (trigstate->dts_tgoid == tgoid)
--- 1782,1788 ----
  	/*
  	 * Lookup if we know an individual state for this trigger
  	 */
! 	foreach(sl, deferredTriggers->state->trigstates)
  	{
  		trigstate = (DeferredTriggerStatus) lfirst(sl);
  		if (trigstate->dts_tgoid == tgoid)
***************
*** 1715,1736 ****
  	 * No individual state known - so if the user issued a SET CONSTRAINT
  	 * ALL ..., we return that instead of the triggers default state.
  	 */
! 	if (deferredTriggers->deftrig_all_isset)
! 		return deferredTriggers->deftrig_all_isdeferred;
  
  	/*
  	 * No ALL state known either, remember the default state as the
  	 * current and return that.
  	 */
! 	oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
  
  	trigstate = (DeferredTriggerStatus)
  		palloc(sizeof(DeferredTriggerStatusData));
  	trigstate->dts_tgoid = tgoid;
  	trigstate->dts_tgisdeferred =
  		((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
! 	deferredTriggers->deftrig_trigstates =
! 		lappend(deferredTriggers->deftrig_trigstates, trigstate);
  
  	MemoryContextSwitchTo(oldcxt);
  
--- 1793,1814 ----
  	 * No individual state known - so if the user issued a SET CONSTRAINT
  	 * ALL ..., we return that instead of the triggers default state.
  	 */
! 	if (deferredTriggers->state->all_isset)
! 		return deferredTriggers->state->all_isdeferred;
  
  	/*
  	 * No ALL state known either, remember the default state as the
  	 * current and return that.
  	 */
! 	oldcxt = MemoryContextSwitchTo(deferredTriggers->memcxt);
  
  	trigstate = (DeferredTriggerStatus)
  		palloc(sizeof(DeferredTriggerStatusData));
  	trigstate->dts_tgoid = tgoid;
  	trigstate->dts_tgisdeferred =
  		((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
! 	deferredTriggers->state->trigstates =
! 		lappend(deferredTriggers->state->trigstates, trigstate);
  
  	MemoryContextSwitchTo(oldcxt);
  
***************
*** 1747,1768 ****
  static void
  deferredTriggerAddEvent(DeferredTriggerEvent event)
  {
! 	/*
! 	 * Since the event list could grow quite long, we keep track of the
! 	 * list tail and append there, rather than just doing a stupid
! 	 * "lappend". This avoids O(N^2) behavior for large numbers of events.
! 	 */
! 	event->dte_next = NULL;
! 	if (deferredTriggers->deftrig_event_tail == NULL)
  	{
  		/* first list entry */
! 		deferredTriggers->deftrig_events = event;
! 		deferredTriggers->deftrig_event_tail = event;
  	}
  	else
  	{
! 		deferredTriggers->deftrig_event_tail->dte_next = event;
! 		deferredTriggers->deftrig_event_tail = event;
  	}
  }
  
--- 1825,1842 ----
  static void
  deferredTriggerAddEvent(DeferredTriggerEvent event)
  {
! 	Assert(event->dte_next == NULL);
! 
! 	if (deferredTriggers->tail_thisxact == NULL)
  	{
  		/* first list entry */
! 		deferredTriggers->events = event;
! 		deferredTriggers->tail_thisxact = event;
  	}
  	else
  	{
! 		deferredTriggers->tail_thisxact->dte_next = event;
! 		deferredTriggers->tail_thisxact = event;
  	}
  }
  
***************
*** 1915,1932 ****
  
  	/*
  	 * If immediate_only is true, then the only events that could need
! 	 * firing are those since deftrig_events_imm.  (But if
! 	 * deftrig_events_imm is NULL, we must scan the entire list.)
  	 */
! 	if (immediate_only && deferredTriggers->deftrig_events_imm != NULL)
  	{
! 		prev_event = deferredTriggers->deftrig_events_imm;
  		event = prev_event->dte_next;
  	}
  	else
  	{
  		prev_event = NULL;
! 		event = deferredTriggers->deftrig_events;
  	}
  
  	while (event != NULL)
--- 1989,2006 ----
  
  	/*
  	 * If immediate_only is true, then the only events that could need
! 	 * firing are those since events_imm.  (But if
! 	 * events_imm is NULL, we must scan the entire list.)
  	 */
! 	if (immediate_only && deferredTriggers->events_imm != NULL)
  	{
! 		prev_event = deferredTriggers->events_imm;
  		event = prev_event->dte_next;
  	}
  	else
  	{
  		prev_event = NULL;
! 		event = deferredTriggers->events;
  	}
  
  	while (event != NULL)
***************
*** 1936,1945 ****
  		int			i;
  
  		/*
! 		 * Check if event is already completely done.
  		 */
! 		if (!(event->dte_event & (TRIGGER_DEFERRED_DONE |
! 								  TRIGGER_DEFERRED_CANCELED)))
  		{
  			MemoryContextReset(per_tuple_context);
  
--- 2010,2022 ----
  		int			i;
  
  		/*
! 		 * Skip executing cancelled events, and events done by transactions
! 		 * that are not aborted.
  		 */
! 		if (!(event->dte_event & TRIGGER_DEFERRED_CANCELED) ||
! 				(event->dte_event & TRIGGER_DEFERRED_DONE &&
! 				 TransactionIdIsValid(event->dte_done_xid) &&
! 				 !TransactionIdDidAbort(event->dte_done_xid)))
  		{
  			MemoryContextReset(per_tuple_context);
  
***************
*** 1948,1954 ****
  			 */
  			for (i = 0; i < event->dte_n_items; i++)
  			{
! 				if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE)
  					continue;
  
  				/*
--- 2025,2033 ----
  			 */
  			for (i = 0; i < event->dte_n_items; i++)
  			{
! 				if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE &&
! 						TransactionIdIsValid(event->dte_item[i].dti_done_xid) &&
! 						!(TransactionIdDidAbort(event->dte_item[i].dti_done_xid)))
  					continue;
  
  				/*
***************
*** 2003,2008 ****
--- 2082,2088 ----
  									   per_tuple_context);
  
  				event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
+ 				event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
  			}					/* end loop over items within event */
  		}
  
***************
*** 2022,2044 ****
  		}
  		else
  		{
! 			/* Done */
! 			if (immediate_only)
  			{
  				/* delink it from list and free it */
  				if (prev_event)
  					prev_event->dte_next = next_event;
  				else
! 					deferredTriggers->deftrig_events = next_event;
  				pfree(event);
  			}
  			else
  			{
  				/*
! 				 * We will clean up later, but just for paranoia's sake,
! 				 * mark the event done.
  				 */
  				event->dte_event |= TRIGGER_DEFERRED_DONE;
  			}
  		}
  
--- 2102,2128 ----
  		}
  		else
  		{
! 			/*
! 			 * We can drop an item if it's done, but only if we're not
! 			 * inside a subtransaction because it could abort later on.
! 			 * We will want to check the item again if it does.
! 			 */
! 			if (immediate_only && !IsSubTransaction())
  			{
  				/* delink it from list and free it */
  				if (prev_event)
  					prev_event->dte_next = next_event;
  				else
! 					deferredTriggers->events = next_event;
  				pfree(event);
  			}
  			else
  			{
  				/*
! 				 * Mark the event done.
  				 */
  				event->dte_event |= TRIGGER_DEFERRED_DONE;
+ 				event->dte_done_xid = GetCurrentTransactionId();
  			}
  		}
  
***************
*** 2046,2055 ****
  	}
  
  	/* Update list tail pointer in case we just deleted tail event */
! 	deferredTriggers->deftrig_event_tail = prev_event;
  
  	/* Set the immediate event pointer for next time */
! 	deferredTriggers->deftrig_events_imm = prev_event;
  
  	/* Release working resources */
  	if (rel)
--- 2130,2139 ----
  	}
  
  	/* Update list tail pointer in case we just deleted tail event */
! 	deferredTriggers->tail_thisxact = prev_event;
  
  	/* Set the immediate event pointer for next time */
! 	deferredTriggers->events_imm = prev_event;
  
  	/* Release working resources */
  	if (rel)
***************
*** 2060,2082 ****
  	MemoryContextDelete(per_tuple_context);
  }
  
- 
- /* ----------
-  * DeferredTriggerInit()
-  *
-  *	Initialize the deferred trigger mechanism. This is called during
-  *	backend startup and is guaranteed to be before the first of all
-  *	transactions.
-  * ----------
-  */
- void
- DeferredTriggerInit(void)
- {
- 	/* Nothing to do */
- 	;
- }
- 
- 
  /* ----------
   * DeferredTriggerBeginXact()
   *
--- 2144,2149 ----
***************
*** 2087,2120 ****
  void
  DeferredTriggerBeginXact(void)
  {
! 	/*
! 	 * This will be changed to a special context when the nested
! 	 * transactions project moves forward.
! 	 */
! 	MemoryContext cxt = TopTransactionContext;
  
! 	deferredTriggers = (DeferredTriggers) MemoryContextAlloc(TopTransactionContext,
! 										   sizeof(DeferredTriggersData));
  
  	/*
  	 * Create the per transaction memory context
  	 */
! 	deferredTriggers->deftrig_cxt = AllocSetContextCreate(cxt,
! 												   "DeferredTriggerXact",
! 												ALLOCSET_DEFAULT_MINSIZE,
! 											   ALLOCSET_DEFAULT_INITSIZE,
! 											   ALLOCSET_DEFAULT_MAXSIZE);
  
  	/*
  	 * If unspecified, constraints default to IMMEDIATE, per SQL
  	 */
! 	deferredTriggers->deftrig_all_isdeferred = false;
! 	deferredTriggers->deftrig_all_isset = false;
  
! 	deferredTriggers->deftrig_trigstates = NIL;
! 	deferredTriggers->deftrig_events = NULL;
! 	deferredTriggers->deftrig_events_imm = NULL;
! 	deferredTriggers->deftrig_event_tail = NULL;
  }
  
  
--- 2154,2185 ----
  void
  DeferredTriggerBeginXact(void)
  {
! 	MemoryContext old_cxt = MemoryContextSwitchTo(TopTransactionContext);
  
! 	deferredTriggers = (DeferredTriggers) palloc(sizeof(DeferredTriggersData));
  
  	/*
  	 * Create the per transaction memory context
  	 */
! 	deferredTriggers->memcxt = AllocSetContextCreate(TopTransactionContext,
! 													 "DeferredTriggerXact",
! 													 ALLOCSET_DEFAULT_MINSIZE,
! 													 ALLOCSET_DEFAULT_INITSIZE,
! 													 ALLOCSET_DEFAULT_MAXSIZE);
  
  	/*
  	 * If unspecified, constraints default to IMMEDIATE, per SQL
  	 */
! 	deferredTriggers->state = DeferredTriggerStateCreate();
! 	deferredTriggers->events = NULL;
! 	deferredTriggers->events_imm = NULL;
! 	deferredTriggers->tail_thisxact = NULL;
! 	deferredTriggers->tail_stack = NULL;
! 	deferredTriggers->imm_stack = NULL;
! 	deferredTriggers->numalloc = 0;
! 	deferredTriggers->numpushed = 0;
  
! 	MemoryContextSwitchTo(old_cxt);
  }
  
  
***************
*** 2183,2188 ****
--- 2248,2446 ----
  	deferredTriggers = NULL;
  }
  
+ /*
+  * DeferredTriggerBeginSubXact()
+  *
+  *	Start a subtransaction.
+  */
+ void
+ DeferredTriggerBeginSubXact(void)
+ {
+ 	/*
+ 	 * Ignore call if the transaction is in aborted state.
+ 	 */
+ 	if (deferredTriggers == NULL)
+ 		return;
+ 
+ 	/*
+ 	 * Reset the saved flag for the new subtransaction.
+ 	 */
+ 	deferredTriggers->state->saved = false;
+ 
+ 	/*
+ 	 * Allocate more space in the stacks if needed.
+ 	 */
+ 	if (deferredTriggers->numpushed == deferredTriggers->numalloc)
+ 	{
+ 		MemoryContext old_cxt;
+ 
+ 		old_cxt = MemoryContextSwitchTo(deferredTriggers->memcxt);
+ 
+ 		if (deferredTriggers->numalloc == 0)
+ 		{
+ #define DEFTRIG_INITALLOC 8
+ 			deferredTriggers->tail_stack =
+ 				palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
+ 
+ 			deferredTriggers->imm_stack =
+ 				palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
+ 
+ 			deferredTriggers->numalloc = DEFTRIG_INITALLOC;
+ 		}
+ 		else
+ 		{
+ 			deferredTriggers->numalloc *= 2;
+ 
+ 			deferredTriggers->tail_stack = 
+ 				repalloc(deferredTriggers->tail_stack,
+ 						 deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
+ 			deferredTriggers->imm_stack =
+ 				repalloc(deferredTriggers->imm_stack,
+ 						deferredTriggers->numalloc * sizeof(DeferredTriggerEvent));
+ 		}
+ 
+ 		MemoryContextSwitchTo(old_cxt);
+ 	}
+ 
+ 	/*
+ 	 * Push the current list position into the stack and reset the
+ 	 * pointer.
+ 	 */
+ 	deferredTriggers->tail_stack[deferredTriggers->numpushed] =
+ 		deferredTriggers->tail_thisxact;
+ 	deferredTriggers->imm_stack[deferredTriggers->numpushed] =
+ 		deferredTriggers->events_imm;
+ 
+ 	deferredTriggers->numpushed++;
+ }
+ 
+ /*
+  * DeferredTriggerEndSubXact()
+  *
+  *	The current subtransaction is aborting.
+  */
+ void
+ DeferredTriggerEndSubXact(bool isCommit)
+ {
+ 	/*
+ 	 * Ignore call if the transaction is in aborted state.
+ 	 */
+ 	if (deferredTriggers == NULL)
+ 		return;
+ 
+ 	/*
+ 	 * Move back the "top of the stack."
+ 	 */
+ 	deferredTriggers->numpushed --;
+ 
+ 	if (!isCommit)
+ 	{
+ 		DeferredTriggerState state;
+ 		/*
+ 		 * Restore the pointers from the stacks.
+ 		 */
+ 		deferredTriggers->tail_thisxact =
+ 			deferredTriggers->tail_stack[deferredTriggers->numpushed];
+ 		deferredTriggers->events_imm =
+ 			deferredTriggers->imm_stack[deferredTriggers->numpushed];
+ 
+ 		/*
+ 		 * Cleanup the head and the tail of the list.
+ 		 */
+ 		if (deferredTriggers->tail_thisxact == NULL)
+ 			deferredTriggers->events = NULL;
+ 		else
+ 			deferredTriggers->tail_thisxact->dte_next = NULL;
+ 
+ 		/*
+ 		 * We don't need to free the items, since the CommitContext will be
+ 		 * reset shortly.
+ 		 */
+ 
+ 		/*
+ 		 * Restore the trigger state.  If the saved state is NULL, then
+ 		 * this subxact didn't save it, so it doesn't need restoring.
+ 		 */
+ 		if ((state = TransactionGetDeftrigState()) != NULL)
+ 		{
+ 			MemoryContextDelete(deferredTriggers->state->memcxt);
+ 			deferredTriggers->state = state;
+ 		}
+ 	}
+ }
+ 
+ static DeferredTriggerState
+ DeferredTriggerStateCreate(void)
+ {
+ 	DeferredTriggerState state;
+ 	MemoryContext		old_cxt;
+ 	MemoryContext		memcxt;
+ 
+ 	/*
+ 	 * Create the DeferredTriggerState memory context
+ 	 */
+ 	memcxt = AllocSetContextCreate(deferredTriggers->memcxt,
+ 								   "DeferredTriggerState",
+ 								   ALLOCSET_SMALL_MINSIZE,
+ 								   ALLOCSET_SMALL_INITSIZE,
+ 								   ALLOCSET_SMALL_MAXSIZE);
+ 
+ 	old_cxt = MemoryContextSwitchTo(memcxt);
+ 
+ 	/*
+ 	 * We assume that zeroing will initialize correctly the state values.
+ 	 *
+ 	 * NB: the state struct is allocated in the state->memcxt trigger.
+ 	 * Be sure not to lose the pointer prior to deleting it ...
+ 	 */
+ 	state = (DeferredTriggerState)
+ 		palloc0(sizeof(DeferredTriggerStateData));
+ 
+ 	state->memcxt = memcxt;
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	return state;
+ }
+ 
+ /*
+  * DeferredTriggerStateCopy
+  */
+ static DeferredTriggerState
+ DeferredTriggerStateCopy(DeferredTriggerState origstate)
+ {
+ 	DeferredTriggerState state;
+ 	MemoryContext		old_cxt;
+ 	ListCell			*cell;
+ 
+ 	state = DeferredTriggerStateCreate();
+ 
+ 	old_cxt = MemoryContextSwitchTo(state->memcxt);
+ 
+ 	state->saved = false;
+ 	state->all_isset = origstate->all_isset;
+ 	state->all_isdeferred = origstate->all_isdeferred;
+ 
+ 	/*
+ 	 * XXX We assume there are not a lot of items on this list.  If there
+ 	 * were, we probably should be using an array of items and not a list
+ 	 * -- this would not only save space on the list itself, but it'd also
+ 	 * make this copying much faster: the whole struct would be a single
+ 	 * memcpy().
+ 	 */
+ 	foreach(cell, origstate->trigstates)
+ 	{
+ 		DeferredTriggerStatus trigstate = (DeferredTriggerStatus)
+ 			palloc(sizeof(DeferredTriggerStatusData));
+ 
+ 		memcpy(trigstate, lfirst(cell), sizeof(DeferredTriggerStatus));
+ 		state->trigstates = lappend(state->trigstates, trigstate);
+ 	}
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	return state;
+ }
  
  /* ----------
   * DeferredTriggerSetState()
***************
*** 2193,2200 ****
  void
  DeferredTriggerSetState(ConstraintsSetStmt *stmt)
  {
- 	ListCell	   *l;
- 
  	/*
  	 * Ignore call if we aren't in a transaction.
  	 */
--- 2451,2456 ----
***************
*** 2202,2207 ****
--- 2458,2473 ----
  		return;
  
  	/*
+ 	 * Save the current state so it can be restored if the subtransaction
+ 	 * aborts.
+ 	 */
+ 	if (IsSubTransaction() && !deferredTriggers->state->saved)
+ 	{
+ 		TransactionSetDeftrigState(DeferredTriggerStateCopy(deferredTriggers->state));
+ 		deferredTriggers->state->saved = true;
+ 	}
+ 
+ 	/*
  	 * Handle SET CONSTRAINTS ALL ...
  	 */
  	if (stmt->constraints == NIL)
***************
*** 2210,2232 ****
  		 * Drop all per-transaction information about individual trigger
  		 * states.
  		 */
! 		list_free_deep(deferredTriggers->deftrig_trigstates);
! 		deferredTriggers->deftrig_trigstates = NIL;
  
  		/*
  		 * Set the per-transaction ALL state to known.
  		 */
! 		deferredTriggers->deftrig_all_isset = true;
! 		deferredTriggers->deftrig_all_isdeferred = stmt->deferred;
  	}
  	else
  	{
  		Relation	tgrel;
  		MemoryContext oldcxt;
! 		bool		found;
! 		DeferredTriggerStatus state;
! 		ListCell   *ls;
! 		List	   *loid = NIL;
  
  		/* ----------
  		 * Handle SET CONSTRAINTS constraint-name [, ...]
--- 2476,2496 ----
  		 * Drop all per-transaction information about individual trigger
  		 * states.
  		 */
! 		list_free_deep(deferredTriggers->state->trigstates);
! 		deferredTriggers->state->trigstates = NIL;
  
  		/*
  		 * Set the per-transaction ALL state to known.
  		 */
! 		deferredTriggers->state->all_isset = true;
! 		deferredTriggers->state->all_isdeferred = stmt->deferred;
  	}
  	else
  	{
  		Relation	tgrel;
  		MemoryContext oldcxt;
! 		ListCell   *l;
! 		List	   *oidlist = NIL;
  
  		/* ----------
  		 * Handle SET CONSTRAINTS constraint-name [, ...]
***************
*** 2241,2246 ****
--- 2505,2511 ----
  			ScanKeyData skey;
  			SysScanDesc tgscan;
  			HeapTuple	htup;
+ 			bool		found;
  
  			/*
  			 * Check that only named constraints are set explicitly
***************
*** 2285,2291 ****
  									cname)));
  
  				constr_oid = HeapTupleGetOid(htup);
! 				loid = lappend_oid(loid, constr_oid);
  				found = true;
  			}
  
--- 2550,2556 ----
  									cname)));
  
  				constr_oid = HeapTupleGetOid(htup);
! 				oidlist = lappend_oid(oidlist, constr_oid);
  				found = true;
  			}
  
***************
*** 2305,2334 ****
  		 * Inside of a transaction block set the trigger states of
  		 * individual triggers on transaction level.
  		 */
! 		oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
  
! 		foreach(l, loid)
  		{
! 			found = false;
! 			foreach(ls, deferredTriggers->deftrig_trigstates)
  			{
! 				state = (DeferredTriggerStatus) lfirst(ls);
! 				if (state->dts_tgoid == lfirst_oid(l))
  				{
! 					state->dts_tgisdeferred = stmt->deferred;
  					found = true;
  					break;
  				}
  			}
  			if (!found)
  			{
! 				state = (DeferredTriggerStatus)
! 					palloc(sizeof(DeferredTriggerStatusData));
! 				state->dts_tgoid = lfirst_oid(l);
! 				state->dts_tgisdeferred = stmt->deferred;
  
! 				deferredTriggers->deftrig_trigstates =
! 					lappend(deferredTriggers->deftrig_trigstates, state);
  			}
  		}
  
--- 2570,2604 ----
  		 * Inside of a transaction block set the trigger states of
  		 * individual triggers on transaction level.
  		 */
! 		oldcxt = MemoryContextSwitchTo(deferredTriggers->state->memcxt);
  
! 		foreach(l, oidlist)
  		{
! 			ListCell   *ls;
! 			bool		found = false;
! 
! 			foreach(ls, deferredTriggers->state->trigstates)
  			{
! 				DeferredTriggerStatus status = (DeferredTriggerStatus) lfirst(ls);
! 				if (status->dts_tgoid == lfirst_oid(l))
  				{
! 					status->dts_tgisdeferred = stmt->deferred;
  					found = true;
  					break;
  				}
  			}
  			if (!found)
  			{
! 				DeferredTriggerStatus status;
! 			   
! 				status = (DeferredTriggerStatus) palloc
! 					(sizeof(DeferredTriggerStatusData));
! 
! 				status->dts_tgoid = lfirst_oid(l);
! 				status->dts_tgisdeferred = stmt->deferred;
  
! 				deferredTriggers->state->trigstates =
! 					lappend(deferredTriggers->state->trigstates, status);
  			}
  		}
  
***************
*** 2347,2360 ****
  	 * entire list, in case some deferred events are now immediately
  	 * invokable.
  	 */
! 	deferredTriggers->deftrig_events_imm = NULL;
  }
  
  
  /* ----------
   * DeferredTriggerSaveEvent()
   *
!  *	Called by ExecAR...Triggers() to add the event to the queue.
   *
   *	NOTE: should be called only if we've determined that an event must
   *	be added to the queue.
--- 2617,2630 ----
  	 * entire list, in case some deferred events are now immediately
  	 * invokable.
  	 */
! 	deferredTriggers->events_imm = NULL;
  }
  
  
  /* ----------
   * DeferredTriggerSaveEvent()
   *
!  *	Called by ExecA[RS]...Triggers() to add the event to the queue.
   *
   *	NOTE: should be called only if we've determined that an event must
   *	be added to the queue.
***************
*** 2423,2431 ****
  		return;
  
  	/*
! 	 * Create a new event
  	 */
! 	oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt);
  
  	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
  		n_enabled_triggers * sizeof(DeferredTriggerEventItem);
--- 2693,2702 ----
  		return;
  
  	/*
! 	 * Create a new event.  We use the CommitContext so the event
! 	 * will automatically go away if the subtransaction aborts.
  	 */
! 	oldcxt = MemoryContextSwitchTo(CommitContext);
  
  	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
  		n_enabled_triggers * sizeof(DeferredTriggerEventItem);
***************
*** 2433,2438 ****
--- 2704,2710 ----
  	new_event = (DeferredTriggerEvent) palloc(new_size);
  	new_event->dte_next = NULL;
  	new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
+ 	new_event->dte_done_xid = InvalidTransactionId;
  	if (row_trigger)
  		new_event->dte_event |= TRIGGER_EVENT_ROW;
  	new_event->dte_relid = rel->rd_id;
***************
*** 2449,2454 ****
--- 2721,2727 ----
  
  		ev_item = &(new_event->dte_item[i]);
  		ev_item->dti_tgoid = trigger->tgoid;
+ 		ev_item->dti_done_xid = InvalidTransactionId;
  		ev_item->dti_state =
  			((trigger->tgdeferrable) ?
  			 TRIGGER_DEFERRED_DEFERRABLE : 0) |
***************
*** 2517,2522 ****
--- 2790,2796 ----
  					 * the trigger at all.
  					 */
  					new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
+ 					new_event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
  				}
  			}
  
#31Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#29)
Re: nested xacts and phantom Xids

On Sun, Jun 27, 2004 at 12:49:10AM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

I'll work on adding the Xid-cache to PGPROC and using that in
TransactionIdIsInProgress and the tqual routines. If you want to work
on that let me know and I'll handle things like the password file, local
bufmgr refcount, etc.

Either one is okay, though doing the latter (ie modules you didn't touch
yet) would be probably a bit easier to merge.

Will do.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"Un poeta es un mundo encerrado en un hombre" (Victor Hugo)

#32Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#23)
Re: nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.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 */

#33Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#32)
Re: nested xacts and phantom Xids

On Tue, Jun 29, 2004 at 03:22:52PM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.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).

Very cool, thank you. I had thought about doing something like this but
in the end I got scared away of changing too much code here.

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?

Nah, it's a leftover from back when there wasn't any way to roll back
GUC vars. I thought it should be handled similarly to the way the
isolation level should be handled. With your patch I think it can be
ripped away entirely.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
Officer Krupke, what are we to do?
Gee, officer Krupke, Krup you! (West Side Story, "Gee, Officer Krupke")

#34Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#8)
2 attachment(s)
Re: nested xacts and phantom Xids

On Sun, Jun 20, 2004 at 08:49:22PM -0400, Tom Lane wrote:

There's a good deal more than that missing :-(. Here are the modules or
actions that are called in CommitTransaction and/or AbortTransaction
that have not yet been touched by the patch:

localbuf.c (refcounts need fixed same as bufmgr)

Here is a patch against the original versions of these files; cleaned up
bufmgr.c somewhat. Adds the same logic to local buffers (moving the
BufferRefCount struct declaration to buf_internals.h so it's shared by
both bufmgr.c and localbuf.c). Needs xact.c and xact.h patched as in
the second patch.

As with the bufmgr.c original patch, I don't really know how to test
that this actually works. I fooled around with printing what it was
doing during a subtrans commit/abort, and it seems OK, but that's about
it. In what situations can a transaction roll back with a nonzero
reference count in a local buffer?

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"I dream about dreams about dreams", sang the nightingale
under the pale moon (Sandman)

Attachments:

buffer.patchtext/plain; charset=us-asciiDownload
Index: src/include/storage/bufmgr.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/include/storage/bufmgr.h,v
retrieving revision 1.82
diff -c -r1.82 bufmgr.h
*** src/include/storage/bufmgr.h	31 May 2004 19:24:05 -0000	1.82
--- src/include/storage/bufmgr.h	21 Jun 2004 20:29:08 -0000
***************
*** 148,153 ****
--- 148,155 ----
  extern char *ShowBufferUsage(void);
  extern void ResetBufferUsage(void);
  extern void AtEOXact_Buffers(bool isCommit);
+ extern void AtSubStart_Buffers(void);
+ extern void AtEOSubXact_Buffers(bool commit);
  extern void FlushBufferPool(void);
  extern BlockNumber BufferGetBlockNumber(Buffer buffer);
  extern BlockNumber RelationGetNumberOfBlocks(Relation relation);
Index: src/include/storage/buf_internals.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/include/storage/buf_internals.h,v
retrieving revision 1.71
diff -c -r1.71 buf_internals.h
*** src/include/storage/buf_internals.h	18 Jun 2004 06:14:13 -0000	1.71
--- src/include/storage/buf_internals.h	29 Jun 2004 13:21:23 -0000
***************
*** 175,180 ****
--- 175,189 ----
  extern long int BufferFlushCount;
  extern long int LocalBufferFlushCount;
  
+ /*
+  * We use a list of this struct to keep track of buffer reference
+  * count checking at subtransaction boundaries.
+  */
+ typedef struct BufferRefCount 
+ {
+ 	Buffer	buffer;
+ 	int		refcount;
+ } BufferRefCount;
  
  /*
   * Bufmgr Interface:
***************
*** 211,215 ****
--- 220,226 ----
  				 bool *foundPtr);
  extern void WriteLocalBuffer(Buffer buffer, bool release);
  extern void AtEOXact_LocalBuffers(bool isCommit);
+ extern void AtSubStart_LocalBuffers(void);
+ extern void AtSubEnd_LocalBuffers(bool isCommit);
  
  #endif   /* BUFMGR_INTERNALS_H */
Index: src/backend/storage/buffer/bufmgr.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/backend/storage/buffer/bufmgr.c,v
retrieving revision 1.171
diff -c -r1.171 bufmgr.c
*** src/backend/storage/buffer/bufmgr.c	18 Jun 2004 06:13:33 -0000	1.171
--- src/backend/storage/buffer/bufmgr.c	29 Jun 2004 20:26:09 -0000
***************
*** 38,43 ****
--- 38,44 ----
  #include <sys/file.h>
  #include <unistd.h>
  
+ #include "access/xact.h"
  #include "lib/stringinfo.h"
  #include "miscadmin.h"
  #include "storage/buf_internals.h"
***************
*** 46,51 ****
--- 47,53 ----
  #include "storage/proc.h"
  #include "storage/smgr.h"
  #include "utils/relcache.h"
+ #include "utils/memutils.h"
  #include "pgstat.h"
  
  
***************
*** 67,72 ****
--- 69,75 ----
  
  static void PinBuffer(BufferDesc *buf);
  static void UnpinBuffer(BufferDesc *buf);
+ static inline void BufferFixLeak(Buffer buffer, int should, bool warn);
  static void WaitIO(BufferDesc *buf);
  static void StartBufferIO(BufferDesc *buf, bool forInput);
  static void TerminateBufferIO(BufferDesc *buf, int err_flag);
***************
*** 826,853 ****
  	for (i = 0; i < NBuffers; i++)
  	{
  		if (PrivateRefCount[i] != 0)
! 		{
! 			BufferDesc *buf = &(BufferDescriptors[i]);
  
! 			if (isCommit)
! 				elog(WARNING,
! 					 "buffer refcount leak: [%03d] "
! 					 "(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
! 					 i,
! 					 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
! 					 buf->tag.rnode.relNode,
! 					 buf->tag.blockNum, buf->flags,
! 					 buf->refcount, PrivateRefCount[i]);
  
! 			PrivateRefCount[i] = 1;		/* make sure we release shared pin */
! 			LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
! 			UnpinBuffer(buf);
! 			LWLockRelease(BufMgrLock);
! 			Assert(PrivateRefCount[i] == 0);
  		}
  	}
  
! 	AtEOXact_LocalBuffers(isCommit);
  }
  
  /*
--- 829,958 ----
  	for (i = 0; i < NBuffers; i++)
  	{
  		if (PrivateRefCount[i] != 0)
! 			BufferFixLeak(i, 0, isCommit);
! 	}
  
! 	AtEOXact_LocalBuffers(isCommit);
! }
  
! /*
!  * During subtransaction start, save buffer reference counts
!  * that are nonzero.
!  */
! void
! AtSubStart_Buffers(void)
! {
! 	List		   *refcounts = NIL;
! 	MemoryContext	old_cxt;
! 	int				i;
! 
! 	/*
! 	 * XXX It would be better to have a per-subxact memory context
! 	 * where we could keep this things.  We don't have one ATM
! 	 * so we free them by hand afterwards.
! 	 */
! 	old_cxt = MemoryContextSwitchTo(TopTransactionContext);
! 
! 	for (i = 0; i < NBuffers; i++)
! 	{
! 		BufferRefCount *brc;
! 
! 		if (PrivateRefCount[i] == 0)
! 			continue;
! 
! 		brc = (BufferRefCount *) palloc(sizeof(BufferRefCount));
! 		brc->buffer = i;
! 		brc->refcount = PrivateRefCount[i];
! 
! 		refcounts = lappend(refcounts, brc);
! 	}
! 
! 	TransactionSetBufferRefcounts(refcounts);
! 
! 	MemoryContextSwitchTo(old_cxt);
! 
! 	AtSubStart_LocalBuffers();
! }
! 
! /*
!  * AtEOSubXact_Buffers
!  *
!  * At subtransaction end, we restore the saved counts.  If committing, we croak
!  * if the refcounts don't match; if aborting, just restore silently.
!  *
!  * This only works because we know the list is in ascending order.
!  */
! void
! AtEOSubXact_Buffers(bool commit)
! {
! 	List	   *counts = TransactionGetBufferRefcounts();
! 	ListCell   *nextElt;
! 	int			i, j;
! 	Buffer		nextSaved;
! 
! 	nextElt   = list_head(counts);
! 
! 	for (i = 0; i < NBuffers; i++)
! 	{
! 		BufferRefCount	   *brc = NULL;
! 
! 		if (nextElt)
! 		{
! 			brc = (BufferRefCount *) lfirst(nextElt);
! 			nextSaved = brc->buffer;
  		}
+ 		else
+ 			nextSaved = NBuffers;
+ 
+ 		/*
+ 		 * Buffers that are not on the saved list are supposed
+ 		 * to have refcount 0.  If they do not, unpin them.
+ 		 * In the commit case, emit a message.
+ 		 */
+ 		for (j = i; j < nextSaved; j++, i++)
+ 			if (PrivateRefCount[j] != 0)
+ 				BufferFixLeak(j, 0, commit);
+ 
+ 		/* No more buffers to check. */
+ 		if (nextSaved == NBuffers)
+ 			break;
+ 
+ 		if (PrivateRefCount[i] != brc->refcount)
+ 			BufferFixLeak(i, brc->refcount, commit);
+ 
+ 		if (nextElt)
+ 			nextElt = nextElt->next;
  	}
  
! 	/* Free the whole list, including the elements. */
! 	list_free_deep(counts);
! 
! 	AtSubEnd_LocalBuffers(commit);
! }
! 
! /*
!  * Fix a buffer refcount leak.
!  */
! inline void
! BufferFixLeak(Buffer buffer, int should, bool emitWarning)
! {
! 	BufferDesc	*buf = &(BufferDescriptors[buffer]);
! 
! 	if (emitWarning)
! 		elog(WARNING,
! 				"buffer refcount leak: [%03d] "
! 				"(rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d, should be=%d)",
! 				buffer,
! 				buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
! 				buf->tag.rnode.relNode,
! 				buf->tag.blockNum, buf->flags,
! 				buf->refcount, PrivateRefCount[buffer], should);
! 
! 	PrivateRefCount[buffer] = 1;		/* make sure we release shared pin */
! 	LWLockAcquire(BufMgrLock, LW_EXCLUSIVE);
! 	UnpinBuffer(buf);
! 	LWLockRelease(BufMgrLock);
! 	Assert(PrivateRefCount[buffer] == 0);
  }
  
  /*
Index: src/backend/storage/buffer/localbuf.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql-server/src/backend/storage/buffer/localbuf.c,v
retrieving revision 1.56
diff -c -r1.56 localbuf.c
*** src/backend/storage/buffer/localbuf.c	18 Jun 2004 06:13:33 -0000	1.56
--- src/backend/storage/buffer/localbuf.c	29 Jun 2004 13:21:39 -0000
***************
*** 15,23 ****
--- 15,25 ----
   */
  #include "postgres.h"
  
+ #include "access/xact.h"
  #include "storage/buf_internals.h"
  #include "storage/bufmgr.h"
  #include "storage/smgr.h"
+ #include "utils/memutils.h"
  #include "utils/relcache.h"
  
  
***************
*** 32,37 ****
--- 34,40 ----
  
  static int	nextFreeLocalBuf = 0;
  
+ static inline void LocalBufferFixLeak(Buffer buffer, int should, bool emitWarning);
  
  /*
   * LocalBufferAlloc -
***************
*** 229,248 ****
  	int			i;
  
  	for (i = 0; i < NLocBuffer; i++)
- 	{
  		if (LocalRefCount[i] != 0)
! 		{
! 			BufferDesc *buf = &(LocalBufferDescriptors[i]);
  
! 			if (isCommit)
! 				elog(WARNING,
! 					 "local buffer leak: [%03d] (rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d)",
! 					 i,
! 					 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
! 					 buf->tag.rnode.relNode, buf->tag.blockNum, buf->flags,
! 					 buf->refcount, LocalRefCount[i]);
  
! 			LocalRefCount[i] = 0;
  		}
  	}
  }
--- 232,343 ----
  	int			i;
  
  	for (i = 0; i < NLocBuffer; i++)
  		if (LocalRefCount[i] != 0)
! 			LocalBufferFixLeak(i, 0, isCommit);
! }
! 
! inline void
! LocalBufferFixLeak(Buffer buffer, int should, bool emitWarning)
! {
! 	BufferDesc *buf = &(LocalBufferDescriptors[buffer]);
! 
! 	if (emitWarning)
! 		elog(WARNING,
! 			 "local buffer leak: [%03d] (rel=%u/%u/%u, blockNum=%u, flags=0x%x, refcount=%u %d should be=%d)",
! 			 buffer,
! 			 buf->tag.rnode.spcNode, buf->tag.rnode.dbNode,
! 			 buf->tag.rnode.relNode, buf->tag.blockNum, buf->flags,
! 			 buf->refcount, LocalRefCount[buffer], should);
! 
! 	LocalRefCount[buffer] = should;
! }
! 
! 
! /*
!  * AtSubStart_LocalBuffers
!  * 
!  * Save reference counts for local buffers.
!  */
! void
! AtSubStart_LocalBuffers(void)
! {
! 	MemoryContext old_cxt;
! 	List   *refcounts = NIL;
! 	int		i;
! 
! 	old_cxt = MemoryContextSwitchTo(TopTransactionContext);
! 
! 	for (i = 0; i < NLocBuffer; i++)
! 	{
! 		BufferRefCount	*brc;
  
! 		if (LocalRefCount[i] == 0)
! 			continue;
! 
! 		brc = (BufferRefCount *) palloc(sizeof(BufferRefCount));
! 		brc->buffer = i;
! 		brc->refcount = LocalRefCount[i];
! 
! 		refcounts = lappend(refcounts, brc);
! 	}
! 
! 	TransactionSetLocalBufferRefcounts(refcounts);
! 
! 	MemoryContextSwitchTo(old_cxt);
! }
  
! /*
!  * AtSubEnd_LocalBuffers
!  *
!  * Restore (and possible croak about) local buffer reference counts
!  * at subtransaction end.
!  */
! void
! AtSubEnd_LocalBuffers(bool commit)
! {
! 	List	   *counts = TransactionGetLocalBufferRefcounts();
! 	ListCell   *nextElt;
! 	int			i, j;
! 	Buffer		nextSaved;
! 
! 	nextElt = list_head(counts);
! 
! 	for (i = 0; i < NLocBuffer; i++)
! 	{
! 		BufferRefCount	*brc = NULL;
! 
! 		/*
! 		 * If there is a next element in this list, skip
! 		 * all local buffers until that element.  If not,
! 		 * skip until the end of the local buffer array.
! 		 */
! 		if (nextElt)
! 		{
! 			brc = (BufferRefCount *) lfirst(nextElt);
! 			nextSaved = brc->buffer;
  		}
+ 		else
+ 			nextSaved = NLocBuffer;
+ 
+ 		/*
+ 		 * Buffers not in the list should have a refcount of 0.
+ 		 */
+ 		for (j = i; j < nextSaved; j++, i++)
+ 			if (LocalRefCount[j] != 0)
+ 				LocalBufferFixLeak(j, 0, commit);
+ 
+ 		/*
+ 		 * Stop here if we are at the end of the array.
+ 		 */
+ 		if (nextSaved == NLocBuffer)
+ 			break;
+ 
+ 		if (LocalRefCount[i] != brc->refcount)
+ 			LocalBufferFixLeak(i, brc->refcount, commit);
+ 
+ 		if (nextElt)
+ 			nextElt = nextElt->next;
  	}
+ 
+ 	list_free_deep(counts);
  }
buffer-xact.patchtext/plain; charset=us-asciiDownload
*** src/backend/access/transam/xact.c~	2004-06-29 16:38:23.468928928 -0400
--- src/backend/access/transam/xact.c	2004-06-29 16:38:29.306995811 -0400
***************
*** 230,235 ****
--- 230,236 ----
  	NIL,						/* async notifies */
  	NIL,						/* committed child Xids */
  	NIL,						/* bufmgr refcounts */
+ 	NIL,						/* local bufmgr refcounts */
  	NIL,						/* GUC variables */
  	NULL,						/* deferred triggers state */
  	0,							/* XLog record offset */
***************
*** 499,504 ****
--- 500,527 ----
  }
  
  /*
+  * TransactionSetLocalBufferRefcounts
+  */
+ void
+ TransactionSetLocalBufferRefcounts(List *counts)
+ {
+ 	TransactionState s = CurrentTransactionState;
+ 
+ 	s->localBufRefCnts = counts;
+ }
+ 
+ /*
+  * TransactionGetLocalBufferRefcounts
+  */
+ List *
+ TransactionGetLocalBufferRefcounts(void)
+ {
+ 	TransactionState s = CurrentTransactionState;
+ 
+ 	return s->localBufRefCnts;
+ }
+ 
+ /*
   * TransactionSaveGucVar
   *
   * This is called from GUC, so be sure to use the right MemoryContext.
*** src/include/access/xact.h~	2004-06-29 16:39:24.256213104 -0400
--- src/include/access/xact.h	2004-06-29 16:39:27.125754456 -0400
***************
*** 97,102 ****
--- 97,103 ----
  	List 		   *notifies;
  	List		   *childXids;
  	List		   *bufRefCnts;
+ 	List		   *localBufRefCnts;
  	List		   *GUCvars;
  	void		   *deftrigState;
  	uint32			XLogRecOff;
***************
*** 165,170 ****
--- 166,173 ----
  extern void TransactionSetParentNotifies(List *);
  extern List *TransactionGetBufferRefcounts(void);
  extern void TransactionSetBufferRefcounts(List *);
+ extern List *TransactionGetLocalBufferRefcounts(void);
+ extern void TransactionSetLocalBufferRefcounts(List *);
  extern void TransactionSaveGucVar(void *);
  extern void TransactionSetDeftrigState(void *);
  extern void *TransactionGetDeftrigState(void);
#35Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#34)
Re: [PATCHES] nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

As with the bufmgr.c original patch, I don't really know how to test
that this actually works. I fooled around with printing what it was
doing during a subtrans commit/abort, and it seems OK, but that's about
it. In what situations can a transaction roll back with a nonzero
reference count in a local buffer?

You need an active cursor, eg

begin;
declare c cursor for select * from tenk1;
fetch 1 in c;
... now you've got an open buffer refcount to some page of tenk1

I forgot to mention to you that that code didn't work at all, btw.
I have fixed some of the problems in my local version but there's still
a fairly large issue, which is what exactly we think the semantics of a
cursor declared in a subtransaction ought to be. With bufmgr set up to
consider open reference counts as a bug, we cannot hold such a cursor
open past subtrans commit.

One possible approach is to consider subxact commit the same as main
xact commit as far as cursors are concerned: materialize anything
declared WITH HOLD, close anything declared without.

The other theory we could adopt is that cursors stay open till main xact
commit; this would imply not releasing buffer refcounts at subxact
commit, plus any other resources needed by the cursor. We're already
holding locks that way and it probably wouldn't be a big change to make
bufmgr work the same. I'm not sure that there are any other resources
involved, other than the Portal memory which we already handle properly.

The first approach is a lower-risk path; I'm not sure if the second one
might have some hidden gotchas. It seems like the second one would be
more flexible though. Any opinions which to pursue?

Oh, there's another point: what happens if an outer xact level declares
a cursor, which is then FETCHed from by a subtransaction? At minimum we
have the problem that this could change the set of buffer pins held,
which breaks the present bufmgr solution entirely. It gets even more
interesting if you are of the opinion that subtransaction failure should
cause the effects of the FETCH to be undone --- we have no way to do
that at all, because there's no mechanism for saving/restoring the state
of an entire execution plan tree. We might have to prohibit
subtransactions from touching outer-level cursors, at least for 7.5.
This would in turn make it a bit questionable whether there's any point
in letting cursors propagate up out of subtransactions...

regards, tom lane

#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#34)
Re: nested xacts and phantom Xids

BTW, here is what I'm working with just at the moment.

regards, tom lane

#37Alvaro Herrera
alvherre@dcc.uchile.cl
In reply to: Tom Lane (#35)
Re: [PATCHES] nested xacts and phantom Xids

On Tue, Jun 29, 2004 at 06:59:20PM -0400, Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

As with the bufmgr.c original patch, I don't really know how to test
that this actually works. [...]

I forgot to mention to you that that code didn't work at all, btw.

Bad news, I guess.

The other theory we could adopt is that cursors stay open till main xact
commit; this would imply not releasing buffer refcounts at subxact
commit, plus any other resources needed by the cursor. We're already
holding locks that way and it probably wouldn't be a big change to make
bufmgr work the same. I'm not sure that there are any other resources
involved, other than the Portal memory which we already handle properly.

Well, AFAIR originally I had thought that refcounts should be held at
subtrans commit; you suggested that there was no reason for a subtrans
to keep a buffer refcount and that was it. I think the open cursor is a
good reason why the count should be kept; it appears less useful if you
can't use the cursor anywhere out of the level that created it.

Oh, there's another point: what happens if an outer xact level declares
a cursor, which is then FETCHed from by a subtransaction? At minimum we
have the problem that this could change the set of buffer pins held,
which breaks the present bufmgr solution entirely. It gets even more
interesting if you are of the opinion that subtransaction failure should
cause the effects of the FETCH to be undone --- we have no way to do
that at all, because there's no mechanism for saving/restoring the state
of an entire execution plan tree.

Hmm ... yes, this could be very ugly indeed, but I haven't even looked
at the executor code so I can't comment. Are executor nodes copyable?

Oh, and I've been playing with large objects and I've encountered bugs
elsewhere. I'll look at it with the new patch you just posted.

--
Alvaro Herrera (<alvherre[a]dcc.uchile.cl>)
"Vivir y dejar de vivir son soluciones imaginarias.
La existencia est� en otra parte" (Andre Breton)

#38Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#37)
Re: [PATCHES] nested xacts and phantom Xids

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Hmm ... yes, this could be very ugly indeed, but I haven't even looked
at the executor code so I can't comment. Are executor nodes copyable?

Nope, and even if we had support for that the executor tree per se
is just the tip of the iceberg. There's also indexscan status, SRF
function internal state, yadda yadda. I think the odds of doing
something with all that stuff for 7.5 are exactly zero ... we'd better
define a stopgap behavior.

Oh, and I've been playing with large objects and I've encountered bugs
elsewhere. I'll look at it with the new patch you just posted.

Wouldn't surprise me, we've not looked at that yet either.

I do feel that we have enough things working that we should commit to
nested transactions for 7.5. There will be some things that we have to
restrict, such as cursors and perhaps large objects. But it's surely
better than no subtransactions at all.

regards, tom lane

#39Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Tom Lane (#38)
Re: nested xacts and phantom Xids

Added to TODO, just so we don't forget later:

* Use a phantom command counter for nested subtransactions to reduce
tuple overhead

---------------------------------------------------------------------------

Tom Lane wrote:

Alvaro Herrera <alvherre@dcc.uchile.cl> writes:

Hmm ... yes, this could be very ugly indeed, but I haven't even looked
at the executor code so I can't comment. Are executor nodes copyable?

Nope, and even if we had support for that the executor tree per se
is just the tip of the iceberg. There's also indexscan status, SRF
function internal state, yadda yadda. I think the odds of doing
something with all that stuff for 7.5 are exactly zero ... we'd better
define a stopgap behavior.

Oh, and I've been playing with large objects and I've encountered bugs
elsewhere. I'll look at it with the new patch you just posted.

Wouldn't surprise me, we've not looked at that yet either.

I do feel that we have enough things working that we should commit to
nested transactions for 7.5. There will be some things that we have to
restrict, such as cursors and perhaps large objects. But it's surely
better than no subtransactions at all.

regards, tom lane

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to majordomo@postgresql.org so that your
message can get through to the mailing list cleanly

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073