Replace nested-BEGIN syntax for subtransactions with spec-compliant
authorTom Lane
Tue, 27 Jul 2004 05:11:48 +0000 (05:11 +0000)
committerTom Lane
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  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;