From cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 27 Jul 2004 05:11:48 +0000
Subject: [PATCH] Replace nested-BEGIN syntax for subtransactions with
 spec-compliant SAVEPOINT/RELEASE/ROLLBACK-TO syntax.  (Alvaro) Cause COMMIT
 of a failed transaction to report ROLLBACK instead of COMMIT in its command
 tag.  (Tom) Fix a few loose ends in the nested-transactions stuff.

---
 src/backend/access/transam/xact.c          | 610 +++++++++++++++++----
 src/backend/executor/spi.c                 |  25 +-
 src/backend/parser/gram.y                  |  36 +-
 src/backend/parser/keywords.c              |   4 +-
 src/backend/storage/lmgr/lmgr.c            |  23 +-
 src/backend/tcop/postgres.c                |  17 +-
 src/backend/tcop/utility.c                 |  52 +-
 src/bin/psql/tab-complete.c                |  17 +-
 src/include/access/xact.h                  |  11 +-
 src/include/nodes/parsenodes.h             |   9 +-
 src/include/utils/errcodes.h               |   6 +-
 src/test/regress/expected/transactions.out | 223 ++++++--
 src/test/regress/sql/transactions.sql      | 171 ++++--
 13 files changed, 929 insertions(+), 275 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d88f7164d34..55d5ef9b80a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.171 2004/07/17 03:28:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.172 2004/07/27 05:10:49 tgl Exp $
  *
  * NOTES
  *		Transaction aborts can now occur two ways:
@@ -186,21 +186,26 @@ typedef enum TransState
  */
 typedef enum TBlockState
 {
+	/* not-in-transaction-block states */
 	TBLOCK_DEFAULT,
 	TBLOCK_STARTED,
+
+	/* transaction block states */
 	TBLOCK_BEGIN,
 	TBLOCK_INPROGRESS,
 	TBLOCK_END,
 	TBLOCK_ABORT,
 	TBLOCK_ENDABORT,
 
+	/* subtransaction states */
 	TBLOCK_SUBBEGIN,
-	TBLOCK_SUBBEGINABORT,
 	TBLOCK_SUBINPROGRESS,
 	TBLOCK_SUBEND,
 	TBLOCK_SUBABORT,
-	TBLOCK_SUBENDABORT_OK,
-	TBLOCK_SUBENDABORT_ERROR
+	TBLOCK_SUBABORT_PENDING,
+	TBLOCK_SUBENDABORT_ALL,
+	TBLOCK_SUBENDABORT_RELEASE,
+	TBLOCK_SUBENDABORT
 } TBlockState;
 
 /*
@@ -209,6 +214,8 @@ typedef enum TBlockState
 typedef struct TransactionStateData
 {
 	TransactionId	transactionIdData;		/* my XID */
+	char		   *name;					/* savepoint name, if any */
+	int				savepointLevel;			/* savepoint level */
 	CommandId		commandId;				/* current CID */
 	TransState		state;					/* low-level state */
 	TBlockState		blockState;				/* high-level state */
@@ -245,6 +252,8 @@ static void CleanupSubTransaction(void);
 static void StartAbortedSubTransaction(void);
 static void PushTransaction(void);
 static void PopTransaction(void);
+static void CommitTransactionToLevel(int level);
+static char *CleanupAbortedSubTransactions(bool returnName);
 
 static void AtSubAbort_Memory(void);
 static void AtSubCleanup_Memory(void);
@@ -264,6 +273,8 @@ static const char *TransStateAsString(TransState state);
  */
 static TransactionStateData TopTransactionStateData = {
 	0,							/* transaction id */
+	NULL,						/* savepoint name */
+	0,							/* savepoint level */
 	FirstCommandId,				/* command id */
 	TRANS_DEFAULT,				/* transaction state */
 	TBLOCK_DEFAULT,				/* transaction block state from the client
@@ -1638,11 +1649,12 @@ StartTransactionCommand(void)
 		case TBLOCK_STARTED:
 		case TBLOCK_BEGIN:
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 		case TBLOCK_END:
 		case TBLOCK_SUBEND:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 		case TBLOCK_ENDABORT:
 			elog(FATAL, "StartTransactionCommand: unexpected state %s",
 				 BlockStateAsString(s->blockState));
@@ -1670,10 +1682,13 @@ CommitTransactionCommand(void)
 			/*
 			 * This shouldn't happen, because it means the previous
 			 * StartTransactionCommand didn't set the STARTED state
-			 * appropiately.
+			 * appropriately, or we didn't manage previous pending
+			 * abort states.
 			 */
 		case TBLOCK_DEFAULT:
-			elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT");
+		case TBLOCK_SUBABORT_PENDING:
+			elog(FATAL, "CommitTransactionCommand: unexpected state %s",
+				 BlockStateAsString(s->blockState));
 			break;
 
 			/*
@@ -1710,6 +1725,12 @@ CommitTransactionCommand(void)
 			 * default state.
 			 */
 		case TBLOCK_END:
+			/* commit all open subtransactions */
+			if (s->nestingLevel > 1)
+				CommitTransactionToLevel(2);
+			s = CurrentTransactionState;
+			Assert(s->parent == NULL);
+			/* and now the outer transaction */
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
 			break;
@@ -1734,7 +1755,17 @@ CommitTransactionCommand(void)
 			break;
 
 			/*
-			 * We were just issued a BEGIN inside a transaction block.
+			 * Ditto, but in a subtransaction.  AbortOutOfAnyTransaction
+			 * will do the dirty work.
+			 */
+		case TBLOCK_SUBENDABORT_ALL:
+			AbortOutOfAnyTransaction();
+			s = CurrentTransactionState;		/* changed by AbortOutOfAnyTransaction */
+			/* AbortOutOfAnyTransaction sets the blockState */
+			break;
+
+			/*
+			 * We were just issued a SAVEPOINT inside a transaction block.
 			 * Start a subtransaction.  (BeginTransactionBlock already
 			 * did PushTransaction, so as to have someplace to put the
 			 * SUBBEGIN state.)
@@ -1744,15 +1775,6 @@ CommitTransactionCommand(void)
 			s->blockState = TBLOCK_SUBINPROGRESS;
 			break;
 
-			/*
-			 * We were issued a BEGIN inside an aborted transaction block.
-			 * Start a subtransaction, and put it in aborted state.
-			 */
-		case TBLOCK_SUBBEGINABORT:
-			StartAbortedSubTransaction();
-			s->blockState = TBLOCK_SUBABORT;
-			break;
-
 			/*
 			 * Inside a subtransaction, increment the command counter.
 			 */
@@ -1761,7 +1783,7 @@ CommitTransactionCommand(void)
 			break;
 
 			/*
-			 * We were issued a COMMIT command, so we end the current
+			 * We were issued a RELEASE command, so we end the current
 			 * subtransaction and return to the parent transaction.
 			 */
 		case TBLOCK_SUBEND:
@@ -1777,29 +1799,80 @@ CommitTransactionCommand(void)
 			break;
 
 			/*
-			 * We are ending an aborted subtransaction via ROLLBACK,
-			 * so the parent can be allowed to live.
+			 * The current subtransaction is ending.  Do the equivalent
+			 * of a ROLLBACK TO followed by a RELEASE command.
 			 */
-		case TBLOCK_SUBENDABORT_OK:
-			CleanupSubTransaction();
-			PopTransaction();
-			s = CurrentTransactionState;		/* changed by pop */
+		case TBLOCK_SUBENDABORT_RELEASE:
+			CleanupAbortedSubTransactions(false);
 			break;
 
 			/*
-			 * We are ending an aborted subtransaction via COMMIT.
-			 * End the subtransaction, and abort the parent too.
+			 * The current subtransaction is ending due to a ROLLBACK
+			 * TO command, so close all savepoints up to the target
+			 * level.  When finished, recreate the savepoint.
 			 */
-		case TBLOCK_SUBENDABORT_ERROR:
-			CleanupSubTransaction();
-			PopTransaction();
-			s = CurrentTransactionState;		/* changed by pop */
-			Assert(s->blockState != TBLOCK_SUBENDABORT_ERROR);
-			AbortCurrentTransaction();
+		case TBLOCK_SUBENDABORT:
+			{
+				char *name = CleanupAbortedSubTransactions(true);
+
+				Assert(PointerIsValid(name));
+				DefineSavepoint(name);
+				s = CurrentTransactionState; /* changed by DefineSavepoint */
+				pfree(name);
+
+				/* This is the same as TBLOCK_SUBBEGIN case */
+				AssertState(s->blockState == TBLOCK_SUBBEGIN);
+				StartSubTransaction();
+				s->blockState = TBLOCK_SUBINPROGRESS;
+			}
 			break;
 	}
 }
 
+/*
+ * CleanupAbortedSubTransactions
+ *
+ * Helper function for CommitTransactionCommand.  Aborts and cleans up
+ * dead subtransactions after a ROLLBACK TO command.  Optionally returns
+ * the name of the last dead subtransaction so it can be reused to redefine
+ * the savepoint.  (Caller is responsible for pfree'ing the result.)
+ */
+static char *
+CleanupAbortedSubTransactions(bool returnName)
+{
+	TransactionState s = CurrentTransactionState;
+	char *name = NULL;
+	
+	AssertState(PointerIsValid(s->parent));
+	Assert(s->parent->blockState == TBLOCK_SUBINPROGRESS ||
+		   s->parent->blockState == TBLOCK_INPROGRESS ||
+		   s->parent->blockState == TBLOCK_SUBABORT_PENDING);
+
+	/*
+	 * Abort everything up to the target level.  The current
+	 * subtransaction only needs cleanup.  If we need to save the name,
+	 * look for the last subtransaction in TBLOCK_SUBABORT_PENDING state.
+	 */
+	if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
+		name = MemoryContextStrdup(TopMemoryContext, s->name);
+
+	CleanupSubTransaction();
+	PopTransaction();
+	s = CurrentTransactionState;		/* changed by pop */
+
+	while (s->blockState == TBLOCK_SUBABORT_PENDING)
+	{
+		AbortSubTransaction();
+		if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
+			name = MemoryContextStrdup(TopMemoryContext, s->name);
+		CleanupSubTransaction();
+		PopTransaction();
+		s = CurrentTransactionState;
+	}
+
+	return name;
+}
+
 /*
  *	AbortCurrentTransaction
  */
@@ -1887,7 +1960,6 @@ AbortCurrentTransaction(void)
 			 * in aborted state.
 			 */
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 			StartAbortedSubTransaction();
 			s->blockState = TBLOCK_SUBABORT;
 			break;
@@ -1902,29 +1974,36 @@ AbortCurrentTransaction(void)
 			 * we have to abort the parent transaction too.
 			 */
 		case TBLOCK_SUBEND:
+		case TBLOCK_SUBABORT_PENDING:
 			AbortSubTransaction();
 			CleanupSubTransaction();
 			PopTransaction();
 			s = CurrentTransactionState;		/* changed by pop */
 			Assert(s->blockState != TBLOCK_SUBEND &&
-					s->blockState != TBLOCK_SUBENDABORT_OK &&
-					s->blockState != TBLOCK_SUBENDABORT_ERROR);
+					s->blockState != TBLOCK_SUBENDABORT);
 			AbortCurrentTransaction();
 			break;
 
 			/*
 			 * Same as above, except the Abort() was already done.
 			 */
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBENDABORT_RELEASE:
 			CleanupSubTransaction();
 			PopTransaction();
 			s = CurrentTransactionState;		/* changed by pop */
 			Assert(s->blockState != TBLOCK_SUBEND &&
-					s->blockState != TBLOCK_SUBENDABORT_OK &&
-					s->blockState != TBLOCK_SUBENDABORT_ERROR);
+					s->blockState != TBLOCK_SUBENDABORT);
 			AbortCurrentTransaction();
 			break;
+
+			/*
+			 * We are already aborting the whole transaction tree.
+			 * Do nothing, CommitTransactionCommand will call
+			 * AbortOutOfAnyTransaction and set things straight.
+			 */
+		case TBLOCK_SUBENDABORT_ALL:
+			break;
 	}
 }
 
@@ -2135,7 +2214,8 @@ BeginTransactionBlock(void)
 {
 	TransactionState s = CurrentTransactionState;
 
-	switch (s->blockState) {
+	switch (s->blockState)
+	{
 			/*
 			 * We are not inside a transaction block, so allow one
 			 * to begin.
@@ -2146,35 +2226,26 @@ BeginTransactionBlock(void)
 
 			/*
 			 * Already a transaction block in progress.
-			 * Start a subtransaction.
 			 */
 		case TBLOCK_INPROGRESS:
 		case TBLOCK_SUBINPROGRESS:
-			PushTransaction();
-			s = CurrentTransactionState;		/* changed by push */
-			s->blockState = TBLOCK_SUBBEGIN;
-			break;
-
-			/*
-			 * An aborted transaction block should be allowed to start
-			 * a subtransaction, but it must put it in aborted state.
-			 */
 		case TBLOCK_ABORT:
 		case TBLOCK_SUBABORT:
-			PushTransaction();
-			s = CurrentTransactionState;		/* changed by push */
-			s->blockState = TBLOCK_SUBBEGINABORT;
+			ereport(WARNING,
+					(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+					 errmsg("there is already a transaction in progress")));
 			break;
 
 			/* These cases are invalid.  Reject them altogether. */
 		case TBLOCK_DEFAULT:
 		case TBLOCK_BEGIN:
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 		case TBLOCK_ENDABORT:
 		case TBLOCK_END:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 		case TBLOCK_SUBEND:
 			elog(FATAL, "BeginTransactionBlock: unexpected state %s",
 				 BlockStateAsString(s->blockState));
@@ -2185,34 +2256,32 @@ BeginTransactionBlock(void)
 /*
  *	EndTransactionBlock
  *		This executes a COMMIT command.
+ *
+ * Since COMMIT may actually do a ROLLBACK, the result indicates what
+ * happened: TRUE for COMMIT, FALSE for ROLLBACK.
  */
-void
+bool
 EndTransactionBlock(void)
 {
 	TransactionState s = CurrentTransactionState;
+	bool		result = false;
 
-	switch (s->blockState) {
+	switch (s->blockState)
+	{
 		/*
-		 * here we are in a transaction block which should commit when we
+		 * We are in a transaction block which should commit when we
 		 * get to the upcoming CommitTransactionCommand() so we set the
 		 * state to "END".	CommitTransactionCommand() will recognize this
-		 * and commit the transaction and return us to the default state
+		 * and commit the transaction and return us to the default state.
 		 */
 		case TBLOCK_INPROGRESS:
-			s->blockState = TBLOCK_END;
-			break;
-
-			/*
-			 * here we are in a subtransaction block.  Signal
-			 * CommitTransactionCommand() to end it and return to the
-			 * parent transaction.
-			 */
 		case TBLOCK_SUBINPROGRESS:
-			s->blockState = TBLOCK_SUBEND;
+			s->blockState = TBLOCK_END;
+			result = true;
 			break;
 
 			/*
-			 * here, we are in a transaction block which aborted. Since the
+			 * We are in a transaction block which aborted. Since the
 			 * AbortTransaction() was already done, we need only
 			 * change to the special "END ABORT" state.  The upcoming
 			 * CommitTransactionCommand() will recognise this and then put us
@@ -2223,13 +2292,12 @@ EndTransactionBlock(void)
 			break;
 
 			/*
-			 * here we are in an aborted subtransaction.  Signal
-			 * CommitTransactionCommand() to clean up and return to the
-			 * parent transaction.  Since the user said COMMIT, we must
-			 * fail the parent transaction.
+			 * Here we are inside an aborted subtransaction.  Go to the "abort
+			 * the whole tree" state so that CommitTransactionCommand() calls
+			 * AbortOutOfAnyTransaction.
 			 */
 		case TBLOCK_SUBABORT:
-			s->blockState = TBLOCK_SUBENDABORT_ERROR;
+			s->blockState = TBLOCK_SUBENDABORT_ALL;
 			break;
 
 		case TBLOCK_STARTED:
@@ -2252,14 +2320,17 @@ EndTransactionBlock(void)
 		case TBLOCK_ENDABORT:
 		case TBLOCK_END:
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 		case TBLOCK_SUBEND:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 			elog(FATAL, "EndTransactionBlock: unexpected state %s",
 				 BlockStateAsString(s->blockState));
 			break;
 	}
+
+	return result;
 }
 
 /*
@@ -2271,27 +2342,32 @@ UserAbortTransactionBlock(void)
 {
 	TransactionState s = CurrentTransactionState;
 
-	switch (s->blockState) {
-		/*
-		 * here we are inside a failed transaction block and we got an abort
-		 * command from the user.  Abort processing is already done, we just
-		 * need to move to the ENDABORT state so we will end up in the default
-		 * state after the upcoming CommitTransactionCommand().
-		 */
+	switch (s->blockState)
+	{
+			/*
+			 * We are inside a failed transaction block and we got an
+			 * abort command from the user.  Abort processing is already
+			 * done, we just need to move to the ENDABORT state so we will
+			 * end up in the default state after the upcoming
+			 * CommitTransactionCommand().
+			 */
 		case TBLOCK_ABORT:
 			s->blockState = TBLOCK_ENDABORT;
 			break;
 
 			/*
-			 * Ditto, for a subtransaction.  Here it is okay to allow the
-			 * parent transaction to continue.
+			 * We are inside a failed subtransaction and we got an
+			 * abort command from the user.  Abort processing is already
+			 * done, so go to the "abort all" state and
+			 * CommitTransactionCommand will call AbortOutOfAnyTransaction
+			 * to set things straight.
 			 */
 		case TBLOCK_SUBABORT:
-			s->blockState = TBLOCK_SUBENDABORT_OK;
+			s->blockState = TBLOCK_SUBENDABORT_ALL;
 			break;
 
 			/*
-			 * here we are inside a transaction block and we got an abort
+			 * We are inside a transaction block and we got an abort
 			 * command from the user, so we move to the ENDABORT state and
 			 * do abort processing so we will end up in the default state
 			 * after the upcoming CommitTransactionCommand().
@@ -2301,17 +2377,22 @@ UserAbortTransactionBlock(void)
 			s->blockState = TBLOCK_ENDABORT;
 			break;
 
-			/* Ditto, for a subtransaction. */
+			/*
+			 * We are inside a subtransaction.  Abort the current
+			 * subtransaction and go to the "abort all" state, so
+			 * CommitTransactionCommand will call AbortOutOfAnyTransaction
+			 * to set things straight.
+			 */
 		case TBLOCK_SUBINPROGRESS:
 			AbortSubTransaction();
-			s->blockState = TBLOCK_SUBENDABORT_OK;
+			s->blockState = TBLOCK_SUBENDABORT_ALL;
 			break;
 
 			/*
-			 * here, the user issued ABORT when not inside a
-			 * transaction. Issue a WARNING and go to abort state.  The
-			 * upcoming call to CommitTransactionCommand() will then put us
-			 * back into the default state.
+			 * The user issued ABORT when not inside a transaction. Issue
+			 * a WARNING and go to abort state.  The upcoming call to
+			 * CommitTransactionCommand() will then put us back into the
+			 * default state.
 			 */
 		case TBLOCK_STARTED:
 			ereport(WARNING,
@@ -2321,21 +2402,265 @@ UserAbortTransactionBlock(void)
 			s->blockState = TBLOCK_ENDABORT;
 			break;
 
-			/* these cases are invalid. */
+			/* These cases are invalid. */
 		case TBLOCK_DEFAULT:
 		case TBLOCK_BEGIN:
 		case TBLOCK_END:
 		case TBLOCK_ENDABORT:
 		case TBLOCK_SUBEND:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 			elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
 				 BlockStateAsString(s->blockState));
 			break;
 	}
+}
+
+/*
+ * DefineSavepoint
+ *		This executes a SAVEPOINT command.
+ */
+void
+DefineSavepoint(char *name)
+{
+	TransactionState	s = CurrentTransactionState;
+
+	switch (s->blockState)
+	{
+		case TBLOCK_INPROGRESS:
+		case TBLOCK_SUBINPROGRESS:
+			/* Normal subtransaction start */
+			PushTransaction();
+			s = CurrentTransactionState;	/* changed by push */
+			/*
+			 * Note that we are allocating the savepoint name in the
+			 * parent transaction's CurTransactionContext, since we
+			 * don't yet have a transaction context for the new guy.
+			 */
+			s->name = MemoryContextStrdup(CurTransactionContext, name);
+			s->blockState = TBLOCK_SUBBEGIN;
+			break;
+
+			/* These cases are invalid.  Reject them altogether. */
+		case TBLOCK_DEFAULT:
+		case TBLOCK_STARTED:
+		case TBLOCK_BEGIN:
+		case TBLOCK_SUBBEGIN:
+		case TBLOCK_ABORT:
+		case TBLOCK_SUBABORT:
+		case TBLOCK_ENDABORT:
+		case TBLOCK_END:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
+		case TBLOCK_SUBEND:
+			elog(FATAL, "BeginTransactionBlock: unexpected state %s",
+				 BlockStateAsString(s->blockState));
+			break;
+	}
+}
+
+/*
+ * ReleaseSavepoint
+ * 		This executes a RELEASE command.
+ */
+void
+ReleaseSavepoint(List *options)
+{
+	TransactionState	s = CurrentTransactionState;
+	TransactionState	target = s;
+	char			   *name = NULL;
+	ListCell		   *cell;
+
+	/*
+	 * Check valid block state transaction status.
+	 */
+	switch (s->blockState)
+	{
+		case TBLOCK_INPROGRESS:
+		case TBLOCK_ABORT:
+			ereport(ERROR,
+					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+					 errmsg("no such savepoint")));
+			break;
+
+			/*
+			 * We are in a non-aborted subtransaction.  This is
+			 * the only valid case.
+			 */
+		case TBLOCK_SUBINPROGRESS:
+			break;
+
+			/* these cases are invalid. */
+		case TBLOCK_DEFAULT:
+		case TBLOCK_STARTED:
+		case TBLOCK_BEGIN:
+		case TBLOCK_ENDABORT:
+		case TBLOCK_END:
+		case TBLOCK_SUBABORT:
+		case TBLOCK_SUBBEGIN:
+		case TBLOCK_SUBEND:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
+			elog(FATAL, "ReleaseSavepoint: unexpected state %s",
+				 BlockStateAsString(s->blockState));
+			break;
+	}
 
+	foreach (cell, options)
+	{
+		DefElem *elem = lfirst(cell);
+
+		if (strcmp(elem->defname, "savepoint_name") == 0)
+			name = strVal(elem->arg);
+	}
+
+	Assert(PointerIsValid(name));
+
+	while (target != NULL)
+	{
+		if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
+			break;
+		target = target->parent;
+	}
+
+	if (!PointerIsValid(target))
+		ereport(ERROR,
+				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+				 errmsg("no such savepoint")));
+
+	CommitTransactionToLevel(target->nestingLevel);
+}
+
+/*
+ * RollbackToSavepoint
+ * 		This executes a ROLLBACK TO <savepoint> command.
+ */
+void
+RollbackToSavepoint(List *options)
+{
+	TransactionState s = CurrentTransactionState;
+	TransactionState target,
+					 xact;
+	ListCell		*cell;
+	char			*name = NULL;
+
+	switch (s->blockState)
+	{
+		/*
+		 * We can't rollback to a savepoint if there is no saveopint
+		 * defined.
+		 */
+		case TBLOCK_ABORT:
+		case TBLOCK_INPROGRESS:
+			ereport(ERROR,
+					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+					 errmsg("no such savepoint")));
+			break;
+
+			/*
+			 * There is at least one savepoint, so proceed.
+			 */
+		case TBLOCK_SUBABORT:
+		case TBLOCK_SUBINPROGRESS:
+			/*
+			 * Have to do AbortSubTransaction, but first check
+			 * if this is the right subtransaction
+			 */
+			break;
+
+			/* these cases are invalid. */
+		case TBLOCK_DEFAULT:
+		case TBLOCK_STARTED:
+		case TBLOCK_BEGIN:
+		case TBLOCK_END:
+		case TBLOCK_ENDABORT:
+		case TBLOCK_SUBEND:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
+		case TBLOCK_SUBBEGIN:
+			elog(FATAL, "RollbackToSavepoint: unexpected state %s",
+				 BlockStateAsString(s->blockState));
+			break;
+	}
+
+	foreach (cell, options)
+	{
+		DefElem *elem = lfirst(cell);
+
+		if (strcmp(elem->defname, "savepoint_name") == 0)
+			name = strVal(elem->arg);
+	}
+
+	Assert(PointerIsValid(name));
+
+	target = CurrentTransactionState;
+
+	while (target != NULL)
+	{
+		if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
+			break;
+		target = target->parent;
+
+		/* we don't cross savepoint level boundaries */
+		if (target->savepointLevel != s->savepointLevel)
+			ereport(ERROR,
+					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+					 errmsg("no such savepoint")));
+	}
+
+	if (!PointerIsValid(target))
+		ereport(ERROR,
+				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+				 errmsg("no such savepoint")));
+
+	/*
+	 * Abort the current subtransaction, if needed.  We can't Cleanup the
+	 * savepoint yet, so signal CommitTransactionCommand to do it and
+	 * close all savepoints up to the target level.
+	 */
+	if (s->blockState == TBLOCK_SUBINPROGRESS)
+		AbortSubTransaction();
+	s->blockState = TBLOCK_SUBENDABORT;
+
+	/*
+	 * Mark "abort pending" all subtransactions up to the target
+	 * subtransaction.  (Except the current subtransaction!)
+	 */
+	xact = CurrentTransactionState;
+
+	while (xact != target)
+	{
+		xact = xact->parent;
+		Assert(PointerIsValid(xact));
+		Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
+		xact->blockState = TBLOCK_SUBABORT_PENDING;
+	}
+}
+
+/*
+ * RollbackAndReleaseSavepoint
+ *
+ * Executes a ROLLBACK TO command, immediately followed by a RELEASE
+ * of the same savepoint.
+ */
+void
+RollbackAndReleaseSavepoint(List *options)
+{
+	TransactionState s;
+
+	RollbackToSavepoint(options);
+	s = CurrentTransactionState;
+	Assert(s->blockState == TBLOCK_SUBENDABORT);
+	s->blockState = TBLOCK_SUBENDABORT_RELEASE;
 }
 
 /*
@@ -2375,7 +2700,6 @@ AbortOutOfAnyTransaction(void)
 				s->blockState = TBLOCK_DEFAULT;
 				break;
 			case TBLOCK_SUBBEGIN:
-			case TBLOCK_SUBBEGINABORT:
 				/*
 				 * We didn't get as far as starting the subxact, so there's
 				 * nothing to abort.  Just pop back to parent.
@@ -2385,6 +2709,7 @@ AbortOutOfAnyTransaction(void)
 				break;
 			case TBLOCK_SUBINPROGRESS:
 			case TBLOCK_SUBEND:
+			case TBLOCK_SUBABORT_PENDING:
 				/* In a subtransaction, so clean it up and abort parent too */
 				AbortSubTransaction();
 				CleanupSubTransaction();
@@ -2392,8 +2717,9 @@ AbortOutOfAnyTransaction(void)
 				s = CurrentTransactionState;		/* changed by pop */
 				break;
 			case TBLOCK_SUBABORT:
-			case TBLOCK_SUBENDABORT_OK:
-			case TBLOCK_SUBENDABORT_ERROR:
+			case TBLOCK_SUBENDABORT_ALL:
+			case TBLOCK_SUBENDABORT:
+			case TBLOCK_SUBENDABORT_RELEASE:
 				/* As above, but AbortSubTransaction already done */
 				CleanupSubTransaction();
 				PopTransaction();
@@ -2406,6 +2732,28 @@ AbortOutOfAnyTransaction(void)
 	Assert(s->parent == NULL);
 }
 
+/*
+ * CommitTransactionToLevel
+ *
+ * Commit everything from the current transaction level
+ * up to the specified level (inclusive).
+ */
+void
+CommitTransactionToLevel(int level)
+{
+	TransactionState s = CurrentTransactionState;
+
+	Assert(s->state == TRANS_INPROGRESS);
+
+	while (s->nestingLevel >= level)
+	{
+		CommitSubTransaction();
+		PopTransaction();
+		s = CurrentTransactionState;				/* changed by pop */
+		Assert(s->state == TRANS_INPROGRESS);
+	}
+}
+
 /*
  * IsTransactionBlock --- are we within a transaction block?
  */
@@ -2461,9 +2809,10 @@ TransactionBlockStatusCode(void)
 		case TBLOCK_ABORT:
 		case TBLOCK_ENDABORT:
 		case TBLOCK_SUBABORT:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
-		case TBLOCK_SUBBEGINABORT:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 			return 'E';			/* in failed transaction */
 	}
 
@@ -2481,7 +2830,8 @@ IsSubTransaction(void)
 {
 	TransactionState s = CurrentTransactionState;
 	
-	switch (s->blockState) {
+	switch (s->blockState)
+	{
 		case TBLOCK_DEFAULT:
 		case TBLOCK_STARTED:
 		case TBLOCK_BEGIN:
@@ -2491,12 +2841,13 @@ IsSubTransaction(void)
 		case TBLOCK_ENDABORT:
 			return false;
 		case TBLOCK_SUBBEGIN:
-		case TBLOCK_SUBBEGINABORT:
 		case TBLOCK_SUBINPROGRESS:
 		case TBLOCK_SUBABORT:
 		case TBLOCK_SUBEND:
-		case TBLOCK_SUBENDABORT_OK:
-		case TBLOCK_SUBENDABORT_ERROR:
+		case TBLOCK_SUBENDABORT_ALL:
+		case TBLOCK_SUBENDABORT:
+		case TBLOCK_SUBABORT_PENDING:
+		case TBLOCK_SUBENDABORT_RELEASE:
 			return true;
 	}
 
@@ -2532,6 +2883,8 @@ StartSubTransaction(void)
 
 	SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
 
+	XactLockTableInsert(s->transactionIdData);
+
 	/*
 	 * Finish setup of other transaction state fields.
 	 */
@@ -2619,6 +2972,9 @@ AbortSubTransaction(void)
 
 	ShowTransactionState("AbortSubTransaction");
 
+	if (s->state != TRANS_INPROGRESS)
+		elog(WARNING, "AbortSubTransaction and not in in-progress state");
+
 	HOLD_INTERRUPTS();
 
 	s->state = TRANS_ABORT;
@@ -2762,6 +3118,9 @@ StartAbortedSubTransaction(void)
 /*
  * PushTransaction
  *		Set up transaction state for a subtransaction
+ *
+ *	The caller has to make sure to always reassign CurrentTransactionState
+ *	if it has a local pointer to it after calling this function.
  */
 static void
 PushTransaction(void)
@@ -2777,6 +3136,7 @@ PushTransaction(void)
 							   sizeof(TransactionStateData));
 	s->parent = p;
 	s->nestingLevel = p->nestingLevel + 1;
+	s->savepointLevel = p->savepointLevel;
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 
@@ -2798,6 +3158,9 @@ PushTransaction(void)
 /*
  * PopTransaction
  *		Pop back to parent transaction state
+ *
+ *	The caller has to make sure to always reassign CurrentTransactionState
+ *	if it has a local pointer to it after calling this function.
  */
 static void
 PopTransaction(void)
@@ -2824,6 +3187,8 @@ PopTransaction(void)
 	CurrentResourceOwner = s->parent->curTransactionOwner;
 
 	/* Free the old child structure */
+	if (s->name)
+		pfree(s->name);
 	pfree(s);
 }
 
@@ -2854,7 +3219,8 @@ ShowTransactionStateRec(TransactionState s)
 
 	/* use ereport to suppress computation if msg will not be printed */
 	ereport(DEBUG2,
-			(errmsg_internal("blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
+			(errmsg_internal("name: %s; blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
+							 PointerIsValid(s->name) ? s->name : "unnamed",
 							 BlockStateAsString(s->blockState),
 							 TransStateAsString(s->state),
 							 (unsigned int) s->transactionIdData,
@@ -2870,7 +3236,8 @@ ShowTransactionStateRec(TransactionState s)
 static const char *
 BlockStateAsString(TBlockState blockState)
 {
-	switch (blockState) {
+	switch (blockState)
+	{
 		case TBLOCK_DEFAULT:
 			return "DEFAULT";
 		case TBLOCK_STARTED:
@@ -2887,18 +3254,20 @@ BlockStateAsString(TBlockState blockState)
 			return "ENDABORT";
 		case TBLOCK_SUBBEGIN:
 			return "SUB BEGIN";
-		case TBLOCK_SUBBEGINABORT:
-			return "SUB BEGIN AB";
 		case TBLOCK_SUBINPROGRESS:
 			return "SUB INPROGRS";
 		case TBLOCK_SUBEND:
 			return "SUB END";
 		case TBLOCK_SUBABORT:
 			return "SUB ABORT";
-		case TBLOCK_SUBENDABORT_OK:
-			return "SUB ENDAB OK";
-		case TBLOCK_SUBENDABORT_ERROR:
-			return "SUB ENDAB ERR";
+		case TBLOCK_SUBENDABORT_ALL:
+			return "SUB ENDAB ALL";
+		case TBLOCK_SUBENDABORT:
+			return "SUB ENDAB";
+		case TBLOCK_SUBABORT_PENDING:
+			return "SUB ABRT PEND";
+		case TBLOCK_SUBENDABORT_RELEASE:
+			return "SUB ENDAB REL";
 	}
 	return "UNRECOGNIZED";
 }
@@ -2910,7 +3279,8 @@ BlockStateAsString(TBlockState blockState)
 static const char *
 TransStateAsString(TransState state)
 {
-	switch (state) {
+	switch (state)
+	{
 		case TRANS_DEFAULT:
 			return "DEFAULT";
 		case TRANS_START:
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 19dbfc13d0f..f2fa0a43163 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.120 2004/07/01 21:17:13 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.121 2004/07/27 05:10:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1181,18 +1181,16 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 					res = SPI_ERROR_CURSOR;
 					goto fail;
 				}
+				else if (IsA(queryTree->utilityStmt, TransactionStmt))
+				{
+					res = SPI_ERROR_TRANSACTION;
+					goto fail;
+				}
 				res = SPI_OK_UTILITY;
 				if (plan == NULL)
 				{
 					ProcessUtility(queryTree->utilityStmt, dest, NULL);
-
-					if (IsA(queryTree->utilityStmt, TransactionStmt))
-					{
-						CommitTransactionCommand();
-						StartTransactionCommand();
-					}
-					else
-						CommandCounterIncrement();
+					CommandCounterIncrement();
 				}
 			}
 			else if (plan == NULL)
@@ -1308,14 +1306,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 			{
 				ProcessUtility(queryTree->utilityStmt, dest, NULL);
 				res = SPI_OK_UTILITY;
-
-				if (IsA(queryTree->utilityStmt, TransactionStmt))
-				{
-					CommitTransactionCommand();
-					StartTransactionCommand();
-				}
-				else
-					CommandCounterIncrement();
+				CommandCounterIncrement();
 			}
 			else
 			{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 519bcce7184..1c7faa2c99d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.467 2004/07/12 05:37:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.468 2004/07/27 05:10:55 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -386,11 +386,11 @@ static void doNegateFloat(Value *v);
 
 	QUOTE
 
-	READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE
-	RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS
-	RULE
+	READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
+	REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT
+	ROLLBACK ROW ROWS RULE
 
-	SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
+	SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
 	SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
 	STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SYSID
@@ -3961,6 +3961,30 @@ TransactionStmt:
 					n->options = NIL;
 					$$ = (Node *)n;
 				}
+			| SAVEPOINT ColId
+				{
+					TransactionStmt *n = makeNode(TransactionStmt);
+					n->kind = TRANS_STMT_SAVEPOINT;
+					n->options = list_make1(makeDefElem("savepoint_name",
+														(Node *)makeString($2)));
+					$$ = (Node *)n;
+				}
+			| RELEASE ColId
+				{
+					TransactionStmt *n = makeNode(TransactionStmt);
+					n->kind = TRANS_STMT_RELEASE;
+					n->options = list_make1(makeDefElem("savepoint_name",
+														(Node *)makeString($2)));
+					$$ = (Node *)n;
+				}
+			| ROLLBACK TO ColId
+				{
+					TransactionStmt *n = makeNode(TransactionStmt);
+					n->kind = TRANS_STMT_ROLLBACK_TO;
+					n->options = list_make1(makeDefElem("savepoint_name",
+														(Node *)makeString($3)));
+					$$ = (Node *)n;
+				}
 		;
 
 opt_transaction:	WORK							{}
@@ -7688,6 +7712,7 @@ unreserved_keyword:
 			| RECHECK
 			| REINDEX
 			| RELATIVE_P
+			| RELEASE
 			| RENAME
 			| REPEATABLE
 			| REPLACE
@@ -7699,6 +7724,7 @@ unreserved_keyword:
 			| ROLLBACK
 			| ROWS
 			| RULE
+			| SAVEPOINT
 			| SCHEMA
 			| SCROLL
 			| SECOND_P
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index cae1ed159b0..80ae597feb5 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.151 2004/07/12 05:37:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.152 2004/07/27 05:10:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -254,6 +254,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"references", REFERENCES},
 	{"reindex", REINDEX},
 	{"relative", RELATIVE_P},
+	{"release", RELEASE},
 	{"rename", RENAME},
 	{"repeatable", REPEATABLE},
 	{"replace", REPLACE},
@@ -267,6 +268,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"row", ROW},
 	{"rows", ROWS},
 	{"rule", RULE},
+	{"savepoint", SAVEPOINT},
 	{"schema", SCHEMA},
 	{"scroll", SCROLL},
 	{"second", SECOND_P},
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 45305b4dea2..176767507c2 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.64 2004/07/01 00:50:59 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.65 2004/07/27 05:10:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -334,21 +334,23 @@ XactLockTableInsert(TransactionId xid)
  *		XactLockTableWait
  *
  * Wait for the specified transaction to commit or abort.
- * We actually wait on the topmost transaction of the transaction tree.
+ *
+ * Note that this does the right thing for subtransactions: if we
+ * wait on a subtransaction, we will be awakened as soon as it aborts
+ * or its parent commits.
  */
 void
 XactLockTableWait(TransactionId xid)
 {
 	LOCKTAG		tag;
 	TransactionId myxid = GetCurrentTransactionId();
-	TransactionId waitXid = SubTransGetTopmostTransaction(xid);
 
-	Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid));
+	Assert(!SubTransXidsHaveCommonAncestor(xid, myxid));
 
 	MemSet(&tag, 0, sizeof(tag));
 	tag.relId = XactLockTableId;
 	tag.dbId = InvalidOid;
-	tag.objId.xid = waitXid;
+	tag.objId.xid = xid;
 
 	if (!LockAcquire(LockTableId, &tag, myxid,
 					 ShareLock, false))
@@ -358,13 +360,8 @@ XactLockTableWait(TransactionId xid)
 
 	/*
 	 * Transaction was committed/aborted/crashed - we have to update
-	 * pg_clog if transaction is still marked as running.  If it's a
-	 * subtransaction, we can update the parent status too.
+	 * pg_clog if transaction is still marked as running.
 	 */
-	if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid))
-	{
-		TransactionIdAbort(waitXid);
-		if (waitXid != xid)
-			TransactionIdAbort(xid);
-	}
+	if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
+		TransactionIdAbort(xid);
 }
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 36fb347de3e..a353122fc26 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.424 2004/07/17 03:29:00 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.425 2004/07/27 05:11:03 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -841,8 +841,8 @@ exec_simple_query(const char *query_string)
 				TransactionStmt *stmt = (TransactionStmt *) parsetree;
 
 				if (stmt->kind == TRANS_STMT_COMMIT ||
-					stmt->kind == TRANS_STMT_BEGIN ||
-					stmt->kind == TRANS_STMT_ROLLBACK)
+					stmt->kind == TRANS_STMT_ROLLBACK ||
+					stmt->kind == TRANS_STMT_ROLLBACK_TO)
 					allowit = true;
 			}
 
@@ -1162,8 +1162,8 @@ exec_parse_message(const char *query_string,	/* string to execute */
 				TransactionStmt *stmt = (TransactionStmt *) parsetree;
 
 				if (stmt->kind == TRANS_STMT_COMMIT ||
-					stmt->kind == TRANS_STMT_BEGIN ||
-					stmt->kind == TRANS_STMT_ROLLBACK)
+					stmt->kind == TRANS_STMT_ROLLBACK ||
+					stmt->kind == TRANS_STMT_ROLLBACK_TO)
 					allowit = true;
 			}
 
@@ -1625,8 +1625,8 @@ exec_execute_message(const char *portal_name, long max_rows)
 
 			is_trans_stmt = true;
 			if (stmt->kind == TRANS_STMT_COMMIT ||
-				stmt->kind == TRANS_STMT_BEGIN ||
-				stmt->kind == TRANS_STMT_ROLLBACK)
+				stmt->kind == TRANS_STMT_ROLLBACK ||
+				stmt->kind == TRANS_STMT_ROLLBACK_TO)
 				is_trans_exit = true;
 		}
 	}
@@ -2810,6 +2810,9 @@ PostgresMain(int argc, char *argv[], const char *username)
 		 */
 		MemoryContextSwitchTo(ErrorContext);
 
+		/* Make sure we are using a sane ResourceOwner, too */
+		CurrentResourceOwner = CurTransactionResourceOwner;
+
 		/* Do the recovery */
 		ereport(DEBUG2,
 				(errmsg_internal("AbortCurrentTransaction")));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a3e727472ab..6c32c6c3d78 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.220 2004/06/25 21:55:57 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.221 2004/07/27 05:11:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -354,12 +354,51 @@ ProcessUtility(Node *parsetree,
 						break;
 
 					case TRANS_STMT_COMMIT:
-						EndTransactionBlock();
+						if (!EndTransactionBlock())
+						{
+							/* report unsuccessful commit in completionTag */
+							if (completionTag)
+								strcpy(completionTag, "ROLLBACK");
+						}
 						break;
 
 					case TRANS_STMT_ROLLBACK:
 						UserAbortTransactionBlock();
 						break;
+
+					case TRANS_STMT_SAVEPOINT:
+						{
+							ListCell *cell;
+							char     *name = NULL;
+
+							RequireTransactionChain((void *)stmt, "SAVEPOINT");
+
+							foreach (cell, stmt->options)
+							{
+								DefElem *elem = lfirst(cell);
+								if (strcmp(elem->defname, "savepoint_name") == 0)
+									name = strVal(elem->arg);
+							}
+
+							Assert(PointerIsValid(name));
+
+							DefineSavepoint(name);
+						}
+						break;
+
+					case TRANS_STMT_RELEASE:
+						RequireTransactionChain((void *)stmt, "RELEASE");
+						ReleaseSavepoint(stmt->options);
+						break;
+
+					case TRANS_STMT_ROLLBACK_TO:
+						RequireTransactionChain((void *)stmt, "ROLLBACK TO");
+						RollbackToSavepoint(stmt->options);
+						/*
+						 * CommitTransactionCommand is in charge
+						 * of re-defining the savepoint again
+						 */
+						break;
 				}
 			}
 			break;
@@ -1114,9 +1153,18 @@ CreateCommandTag(Node *parsetree)
 						break;
 
 					case TRANS_STMT_ROLLBACK:
+					case TRANS_STMT_ROLLBACK_TO:
 						tag = "ROLLBACK";
 						break;
 
+					case TRANS_STMT_SAVEPOINT:
+						tag = "SAVEPOINT";
+						break;
+
+					case TRANS_STMT_RELEASE:
+						tag = "RELEASE";
+						break;
+
 					default:
 						tag = "???";
 						break;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index f26c3e75337..0dfaebe38b0 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.107 2004/05/26 13:56:55 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.108 2004/07/27 05:11:11 tgl Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -463,8 +463,8 @@ psql_completion(char *text, int start, int end)
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
 		"COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE",
 		"EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY",
-		"PREPARE", "REINDEX", "RESET", "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", "START",
-		"TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
+		"PREPARE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT", 
+                "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
 	};
 
 	static const char * const pgsql_variables[] = {
@@ -722,16 +722,23 @@ psql_completion(char *text, int start, int end)
 	else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0)
 		COMPLETE_WITH_CONST(";");
 
-/* BEGIN, COMMIT, ROLLBACK, ABORT, */
+/* BEGIN, COMMIT, ABORT */
 	else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
 	         pg_strcasecmp(prev_wd, "END") == 0 ||
 	         pg_strcasecmp(prev_wd, "COMMIT") == 0 ||
-	         pg_strcasecmp(prev_wd, "ROLLBACK") == 0 ||
 	         pg_strcasecmp(prev_wd, "ABORT") == 0)
 	{
 		static const char * const list_TRANS[] =
 		{"WORK", "TRANSACTION", NULL};
 
+		COMPLETE_WITH_LIST(list_TRANS);
+	}
+/* ROLLBACK*/
+	else if ( pg_strcasecmp(prev_wd, "ROLLBACK") == 0 )
+	{
+		static const char * const list_TRANS[] =
+		{"WORK", "TRANSACTION", "TO", NULL};
+
 		COMPLETE_WITH_LIST(list_TRANS);
 	}
 /* CLUSTER */
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 458b3012adf..7bf92b0153d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.66 2004/07/21 22:31:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.67 2004/07/27 05:11:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,7 @@
 
 #include "access/xlog.h"
 #include "storage/relfilenode.h"
+#include "nodes/pg_list.h"
 #include "utils/nabstime.h"
 
 
@@ -101,12 +102,16 @@ extern void StartTransactionCommand(void);
 extern void CommitTransactionCommand(void);
 extern void AbortCurrentTransaction(void);
 extern void BeginTransactionBlock(void);
-extern void EndTransactionBlock(void);
+extern bool EndTransactionBlock(void);
+extern void UserAbortTransactionBlock(void);
+extern void ReleaseSavepoint(List *options);
+extern void DefineSavepoint(char *name);
+extern void RollbackToSavepoint(List *options);
+extern void RollbackAndReleaseSavepoint(List *options);
 extern bool IsSubTransaction(void);
 extern bool IsTransactionBlock(void);
 extern bool IsTransactionOrTransactionBlock(void);
 extern char TransactionBlockStatusCode(void);
-extern void UserAbortTransactionBlock(void);
 extern void AbortOutOfAnyTransaction(void);
 extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
 extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 47b09b42a79..9abfdb86054 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.262 2004/07/12 05:38:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.263 2004/07/27 05:11:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1514,14 +1514,17 @@ typedef enum TransactionStmtKind
 	TRANS_STMT_BEGIN,
 	TRANS_STMT_START,			/* semantically identical to BEGIN */
 	TRANS_STMT_COMMIT,
-	TRANS_STMT_ROLLBACK
+	TRANS_STMT_ROLLBACK,
+	TRANS_STMT_SAVEPOINT,
+	TRANS_STMT_RELEASE,
+	TRANS_STMT_ROLLBACK_TO
 } TransactionStmtKind;
 
 typedef struct TransactionStmt
 {
 	NodeTag		type;
 	TransactionStmtKind kind;	/* see above */
-	List	   *options;		/* for BEGIN/START only */
+	List	   *options;		/* for BEGIN/START and savepoint commands */
 } TransactionStmt;
 
 /* ----------------------
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index 270eb2073bc..f3ac7f6c0b4 100644
--- a/src/include/utils/errcodes.h
+++ b/src/include/utils/errcodes.h
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 2003, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.12 2004/06/01 21:49:22 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.13 2004/07/27 05:11:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -213,6 +213,10 @@
 #define ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED	MAKE_SQLSTATE('3','9', 'P','0','1')
 #define ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED	MAKE_SQLSTATE('3','9', 'P','0','2')
 
+/* Class 3B - Savepoint Exception */
+#define ERRCODE_SAVEPOINT_EXCEPTION			MAKE_SQLSTATE('3','B', '0','0','0')
+#define ERRCODE_S_E_INVALID_SPECIFICATION	MAKE_SQLSTATE('3','B', '0','0','1')
+
 /* Class 3D - Invalid Catalog Name */
 #define ERRCODE_INVALID_CATALOG_NAME		MAKE_SQLSTATE('3','D', '0','0','0')
 
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index cc3004dbb28..d5a2c0c5fa8 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -74,13 +74,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
 CREATE TABLE foobar (a int);
 BEGIN;
 	CREATE TABLE foo (a int);
-	BEGIN;
+	SAVEPOINT one;
 		DROP TABLE foo;
 		CREATE TABLE bar (a int);
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
 		CREATE TABLE baz (a int);
-	COMMIT;
+	RELEASE two;
 	drop TABLE foobar;
 	CREATE TABLE barbaz (a int);
 COMMIT;
@@ -105,18 +106,20 @@ SELECT * FROM baz;		-- should be empty
 -- inserts
 BEGIN;
 	INSERT INTO foo VALUES (1);
-	BEGIN;
+	SAVEPOINT one;
 		INSERT into bar VALUES (1);
 ERROR:  relation "bar" does not exist
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
 		INSERT into barbaz VALUES (1);
-	COMMIT;
-	BEGIN;
-		BEGIN;
+	RELEASE two;
+	SAVEPOINT three;
+		SAVEPOINT four;
 			INSERT INTO foo VALUES (2);
-		COMMIT;
-	ROLLBACK;
+		RELEASE four;
+	ROLLBACK TO three;
+	RELEASE three;
 	INSERT INTO foo VALUES (3);
 COMMIT;
 SELECT * FROM foo;		-- should have 1 and 3
@@ -132,53 +135,168 @@ SELECT * FROM barbaz;	-- should have 1
  1
 (1 row)
 
--- check that starting a subxact in a failed xact or subxact works
+-- test whole-tree commit
 BEGIN;
-	SELECT 0/0;		-- fail the outer xact
-ERROR:  division by zero
-	BEGIN;
-		SELECT 1;	-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
-	COMMIT;
-	SELECT 1;		-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
-	BEGIN;
-		SELECT 1;	-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
-	ROLLBACK;
-	SELECT 1;		-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
+	SAVEPOINT one;
+		SELECT foo;
+ERROR:  column "foo" does not exist
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
+		CREATE TABLE savepoints (a int);
+		SAVEPOINT three;
+			INSERT INTO savepoints VALUES (1);
+			SAVEPOINT four;
+				INSERT INTO savepoints VALUES (2);
+				SAVEPOINT five;
+					INSERT INTO savepoints VALUES (3);
+				ROLLBACK TO five;
 COMMIT;
-SELECT 1;			-- this should work
+COMMIT;		-- should not be in a transaction block
+WARNING:  there is no transaction in progress
+SELECT * FROM savepoints;
+ a 
+---
+ 1
+ 2
+(2 rows)
+
+-- test whole-tree rollback
+BEGIN;
+	SAVEPOINT one;
+		DELETE FROM savepoints WHERE a=1;
+	RELEASE one;
+	SAVEPOINT two;
+		DELETE FROM savepoints WHERE a=1;
+		SAVEPOINT three;
+			DELETE FROM savepoints WHERE a=2;
+ROLLBACK;
+COMMIT;		-- should not be in a transaction block
+WARNING:  there is no transaction in progress
+		
+SELECT * FROM savepoints;
+ a 
+---
+ 1
+ 2
+(2 rows)
+
+-- test whole-tree commit on an aborted subtransaction
+BEGIN;
+	INSERT INTO savepoints VALUES (4);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (5);
+		SELECT foo;
+ERROR:  column "foo" does not exist
+COMMIT;
+SELECT * FROM savepoints;
+ a 
+---
+ 1
+ 2
+(2 rows)
+
+BEGIN;
+	INSERT INTO savepoints VALUES (6);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (7);
+	RELEASE one;
+	INSERT INTO savepoints VALUES (8);
+COMMIT;
+-- rows 6 and 8 should have been created by the same xact
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8;
  ?column? 
 ----------
-        1
+ t
+(1 row)
+
+-- rows 6 and 7 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7;
+ ?column? 
+----------
+ f
 (1 row)
 
 BEGIN;
-	BEGIN;
-		SELECT 1;	-- this should work
+	INSERT INTO savepoints VALUES (9);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (10);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (11);
+COMMIT;
+SELECT a FROM savepoints WHERE a in (9, 10, 11);
+ a  
+----
+  9
+ 11
+(2 rows)
+
+-- rows 9 and 11 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11;
  ?column? 
 ----------
-        1
+ f
 (1 row)
 
-		SELECT 0/0;	-- fail the subxact
+BEGIN;
+	INSERT INTO savepoints VALUES (12);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (13);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (14);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (15);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (16);
+			SAVEPOINT three;
+				INSERT INTO savepoints VALUES (17);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17;
+ a  
+----
+ 12
+ 15
+ 16
+ 17
+(4 rows)
+
+BEGIN;
+	INSERT INTO savepoints VALUES (18);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (19);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (20);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (21);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (22);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22;
+ a  
+----
+ 18
+ 22
+(2 rows)
+
+DROP TABLE savepoints;
+-- only in a transaction block:
+SAVEPOINT one;
+ERROR:  SAVEPOINT may only be used in transaction blocks
+ROLLBACK TO one;
+ERROR:  ROLLBACK TO may only be used in transaction blocks
+RELEASE one;
+ERROR:  RELEASE may only be used in transaction blocks
+-- Only "rollback to" allowed in aborted state
+BEGIN;
+  SAVEPOINT one;
+  SELECT 0/0;
 ERROR:  division by zero
-		SELECT 1;	-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
-		BEGIN;
-			SELECT 1;	-- this should NOT work
-ERROR:  current transaction is aborted, commands ignored until end of transaction block
-		ROLLBACK;
-		BEGIN;
-			SELECT 1;	-- this should NOT work
+  SAVEPOINT two;    -- ignored till the end of ...
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
-		COMMIT;
-		SELECT 1;	-- this should NOT work
+  RELEASE one;      -- ignored till the end of ...
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
-	ROLLBACK;
-	SELECT 1;		-- this should work
+  ROLLBACK TO one;
+  SELECT 1;
  ?column? 
 ----------
         1
@@ -194,7 +312,7 @@ SELECT 1;			-- this should work
 -- check non-transactional behavior of cursors
 BEGIN;
 	DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
-	BEGIN;
+	SAVEPOINT one;
 		FETCH 10 FROM c;
  unique2 
 ---------
@@ -210,8 +328,7 @@ BEGIN;
        9
 (10 rows)
 
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
 		FETCH 10 FROM c;
  unique2 
 ---------
@@ -227,7 +344,7 @@ BEGIN;
       19
 (10 rows)
 
-	COMMIT;
+	RELEASE one;
 	FETCH 10 FROM c;
  unique2 
 ---------
@@ -245,15 +362,15 @@ BEGIN;
 
 	CLOSE c;
 	DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
-	BEGIN;
+	SAVEPOINT two;
 		FETCH 10 FROM c;
 ERROR:  division by zero
-	ROLLBACK;
+	ROLLBACK TO two;
 	-- c is now dead to the world ...
-	BEGIN;
 		FETCH 10 FROM c;
 ERROR:  portal "c" cannot be run
-	ROLLBACK;
+	ROLLBACK TO two;
+	RELEASE two;
 	FETCH 10 FROM c;
 ERROR:  portal "c" cannot be run
 COMMIT;
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index f2a206979fe..d101ff305dd 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -61,13 +61,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
 CREATE TABLE foobar (a int);
 BEGIN;
 	CREATE TABLE foo (a int);
-	BEGIN;
+	SAVEPOINT one;
 		DROP TABLE foo;
 		CREATE TABLE bar (a int);
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
 		CREATE TABLE baz (a int);
-	COMMIT;
+	RELEASE two;
 	drop TABLE foobar;
 	CREATE TABLE barbaz (a int);
 COMMIT;
@@ -80,76 +81,156 @@ SELECT * FROM baz;		-- should be empty
 -- inserts
 BEGIN;
 	INSERT INTO foo VALUES (1);
-	BEGIN;
+	SAVEPOINT one;
 		INSERT into bar VALUES (1);
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
 		INSERT into barbaz VALUES (1);
-	COMMIT;
-	BEGIN;
-		BEGIN;
+	RELEASE two;
+	SAVEPOINT three;
+		SAVEPOINT four;
 			INSERT INTO foo VALUES (2);
-		COMMIT;
-	ROLLBACK;
+		RELEASE four;
+	ROLLBACK TO three;
+	RELEASE three;
 	INSERT INTO foo VALUES (3);
 COMMIT;
 SELECT * FROM foo;		-- should have 1 and 3
 SELECT * FROM barbaz;	-- should have 1
 
--- check that starting a subxact in a failed xact or subxact works
+-- test whole-tree commit
 BEGIN;
-	SELECT 0/0;		-- fail the outer xact
-	BEGIN;
-		SELECT 1;	-- this should NOT work
-	COMMIT;
-	SELECT 1;		-- this should NOT work
-	BEGIN;
-		SELECT 1;	-- this should NOT work
-	ROLLBACK;
-	SELECT 1;		-- this should NOT work
+	SAVEPOINT one;
+		SELECT foo;
+	ROLLBACK TO one;
+	RELEASE one;
+	SAVEPOINT two;
+		CREATE TABLE savepoints (a int);
+		SAVEPOINT three;
+			INSERT INTO savepoints VALUES (1);
+			SAVEPOINT four;
+				INSERT INTO savepoints VALUES (2);
+				SAVEPOINT five;
+					INSERT INTO savepoints VALUES (3);
+				ROLLBACK TO five;
 COMMIT;
-SELECT 1;			-- this should work
+COMMIT;		-- should not be in a transaction block
+SELECT * FROM savepoints;
+
+-- test whole-tree rollback
+BEGIN;
+	SAVEPOINT one;
+		DELETE FROM savepoints WHERE a=1;
+	RELEASE one;
+	SAVEPOINT two;
+		DELETE FROM savepoints WHERE a=1;
+		SAVEPOINT three;
+			DELETE FROM savepoints WHERE a=2;
+ROLLBACK;
+COMMIT;		-- should not be in a transaction block
+		
+SELECT * FROM savepoints;
+
+-- test whole-tree commit on an aborted subtransaction
+BEGIN;
+	INSERT INTO savepoints VALUES (4);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (5);
+		SELECT foo;
+COMMIT;
+SELECT * FROM savepoints;
+
+BEGIN;
+	INSERT INTO savepoints VALUES (6);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (7);
+	RELEASE one;
+	INSERT INTO savepoints VALUES (8);
+COMMIT;
+-- rows 6 and 8 should have been created by the same xact
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8;
+-- rows 6 and 7 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7;
+
+BEGIN;
+	INSERT INTO savepoints VALUES (9);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (10);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (11);
+COMMIT;
+SELECT a FROM savepoints WHERE a in (9, 10, 11);
+-- rows 9 and 11 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11;
+
+BEGIN;
+	INSERT INTO savepoints VALUES (12);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (13);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (14);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (15);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (16);
+			SAVEPOINT three;
+				INSERT INTO savepoints VALUES (17);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17;
+
+BEGIN;
+	INSERT INTO savepoints VALUES (18);
+	SAVEPOINT one;
+		INSERT INTO savepoints VALUES (19);
+		SAVEPOINT two;
+			INSERT INTO savepoints VALUES (20);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (21);
+	ROLLBACK TO one;
+		INSERT INTO savepoints VALUES (22);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22;
+
+DROP TABLE savepoints;
 
+-- only in a transaction block:
+SAVEPOINT one;
+ROLLBACK TO one;
+RELEASE one;
+
+-- Only "rollback to" allowed in aborted state
 BEGIN;
-	BEGIN;
-		SELECT 1;	-- this should work
-		SELECT 0/0;	-- fail the subxact
-		SELECT 1;	-- this should NOT work
-		BEGIN;
-			SELECT 1;	-- this should NOT work
-		ROLLBACK;
-		BEGIN;
-			SELECT 1;	-- this should NOT work
-		COMMIT;
-		SELECT 1;	-- this should NOT work
-	ROLLBACK;
-	SELECT 1;		-- this should work
+  SAVEPOINT one;
+  SELECT 0/0;
+  SAVEPOINT two;    -- ignored till the end of ...
+  RELEASE one;      -- ignored till the end of ...
+  ROLLBACK TO one;
+  SELECT 1;
 COMMIT;
 SELECT 1;			-- this should work
 
 -- check non-transactional behavior of cursors
 BEGIN;
 	DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
-	BEGIN;
+	SAVEPOINT one;
 		FETCH 10 FROM c;
-	ROLLBACK;
-	BEGIN;
+	ROLLBACK TO one;
 		FETCH 10 FROM c;
-	COMMIT;
+	RELEASE one;
 	FETCH 10 FROM c;
 	CLOSE c;
 	DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
-	BEGIN;
+	SAVEPOINT two;
 		FETCH 10 FROM c;
-	ROLLBACK;
+	ROLLBACK TO two;
 	-- c is now dead to the world ...
-	BEGIN;
 		FETCH 10 FROM c;
-	ROLLBACK;
+	ROLLBACK TO two;
+	RELEASE two;
 	FETCH 10 FROM c;
 COMMIT;
 
-
 DROP TABLE foo;
 DROP TABLE baz;
 DROP TABLE barbaz;
-- 
GitLab