]> granicus.if.org Git - postgresql/commitdiff
Replace nested-BEGIN syntax for subtransactions with spec-compliant
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 27 Jul 2004 05:11:48 +0000 (05:11 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 27 Jul 2004 05:11:48 +0000 (05:11 +0000)
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.

13 files changed:
src/backend/access/transam/xact.c
src/backend/executor/spi.c
src/backend/parser/gram.y
src/backend/parser/keywords.c
src/backend/storage/lmgr/lmgr.c
src/backend/tcop/postgres.c
src/backend/tcop/utility.c
src/bin/psql/tab-complete.c
src/include/access/xact.h
src/include/nodes/parsenodes.h
src/include/utils/errcodes.h
src/test/regress/expected/transactions.out
src/test/regress/sql/transactions.sql

index d88f7164d34a5308379960ccb15f4742e02ba4f8..55d5ef9b80ac8a5cbc9f702395f8dcef82ce5867 100644 (file)
@@ -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:
index 19dbfc13d0f3b9ece13cfdf503886d42a80f844b..f2fa0a43163ab5c12fd7b0bd693fa23fe741c575 100644 (file)
@@ -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
                        {
index 519bcce7184b654d19be963b3ac456b7ae25d450..1c7faa2c99d0c82ed64457495e0b09aa278625f7 100644 (file)
@@ -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
index cae1ed159b0c496ed83cc61504e1fe2bcedfdb38..80ae597feb5a415f6b2f60ebf7ccd6b229c8bd00 100644 (file)
@@ -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},
index 45305b4dea2f90c469dbbc7390e69429d76fd5fc..176767507c2d2c8c8a15c7d64564325a91b9dd46 100644 (file)
@@ -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);
 }
index 36fb347de3e810b7c12afab1423512c61f2ee114..a353122fc26921422ac4841bf5f030a33d30f9af 100644 (file)
@@ -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")));
index a3e727472abbc0e366dbb14bcdab6901de07c52e..6c32c6c3d788117588df3058dcecf75a7fd24f03 100644 (file)
@@ -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;
index f26c3e7533762dcb91a2c17c593a042535383dc4..0dfaebe38b01cc37e2629106dffbc2a32a08fe40 100644 (file)
@@ -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 */
index 458b3012adfcf5600cdf12e66295cd47291d1345..7bf92b0153de8b6fa092150e191099ebbcdb94ee 100644 (file)
@@ -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);
index 47b09b42a79c2b5687bc94ac7da5e46e579532ea..9abfdb86054052fa9722b574e420b2b71b83dc13 100644 (file)
@@ -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;
 
 /* ----------------------
index 270eb2073bc7484443ede42134f9793da4235377..f3ac7f6c0b4b94a92cb252bc952ae2bec60bf595 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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')
 
index cc3004dbb28d505371654fec3f78a24785ffbc5d..d5a2c0c5fa860c831e64f1411523ca6267c38f8c 100644 (file)
@@ -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;
index f2a206979fe076ab9400075e851384be87a929a5..d101ff305dd9310f9e4bac4c43e12db8e1466b35 100644 (file)
@@ -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;