@@ -97,6 +97,7 @@ module Database.PostgreSQL.Simple
97-- , Base.insertID
97-- , Base.insertID
98 -- * Transaction handling
98 -- * Transaction handling
99 , withTransaction
99 , withTransaction
100+ , withTransactionSerializable
100 , TransactionMode(..)
101 , TransactionMode(..)
101 , IsolationLevel(..)
102 , IsolationLevel(..)
102 , ReadWriteMode(..)
103 , ReadWriteMode(..)
@@ -105,6 +106,7 @@ module Database.PostgreSQL.Simple
105 , defaultReadWriteMode
106 , defaultReadWriteMode
106 , withTransactionLevel
107 , withTransactionLevel
107 , withTransactionMode
108 , withTransactionMode
109+ , withTransactionModeRetry
108-- , Base.autocommit
110-- , Base.autocommit
110 , beginLevel
112 , beginLevel
@@ -122,7 +124,8 @@ import Blaze.ByteString.Builder.Char8 (fromChar)
122import Control.Applicative ((<$>), pure)
124import Control.Applicative ((<$>), pure)
123import Control.Concurrent.MVar
125import Control.Concurrent.MVar
124import Control.Exception
126import Control.Exception
125− ( Exception, onException, throw, throwIO, finally )
127+ ( Exception, onException, throw, throwIO, finally
128+ , try, SomeException, fromException )
126import Control.Monad (foldM)
129import Control.Monad (foldM)
127import Data.ByteString (ByteString)
130import Data.ByteString (ByteString)
128import Data.Int (Int64)
131import Data.Int (Int64)
@@ -643,6 +646,24 @@ defaultReadWriteMode = DefaultReadWriteMode
643withTransaction :: Connection -> IO a -> IO a
646withTransaction :: Connection -> IO a -> IO a
644withTransaction = withTransactionMode defaultTransactionMode
647withTransaction = withTransactionMode defaultTransactionMode
649+-- | Execute an action inside of a 'Serializable' transaction. If a
650+-- serialization failure occurs, roll back the transaction and try again.
651+-- Be warned that this may execute the IO action multiple times.
653+-- A 'Serializable' transaction creates the illusion that your program has
654+-- exclusive access to the database. This means that, even in a concurrent
655+-- setting, you can perform queries in sequence without having to worry about
656+-- what might happen between one statement and the next.
658+-- Think of it as STM, but without @retry@.
659+withTransactionSerializable :: Connection -> IO a -> IO a
660+withTransactionSerializable =
661+ withTransactionModeRetry
663+ { isolationLevel = Serializable
664+ , readWriteMode = ReadWrite
646-- | Execute an action inside a SQL transaction with a given isolation level.
667-- | Execute an action inside a SQL transaction with a given isolation level.
647withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a
668withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a
648withTransactionLevel lvl
669withTransactionLevel lvl
@@ -657,6 +678,37 @@ withTransactionMode mode conn act =
657 commit conn
678 commit conn
681+-- | Like 'withTransactionMode', but if a 'SqlError' arises whose 'sqlState' is
682+-- @\"40001\"@ (@serialization_failure@), this will issue a @ROLLBACK@, then
683+-- try the action again. If any other exception arises, this will issue a
684+-- @ROLLBACK@, but will propagate the exception instead of retrying.
686+-- This is used to implement 'withTransactionSerializable'.
687+withTransactionModeRetry :: TransactionMode -> Connection -> IO a -> IO a
688+withTransactionModeRetry mode conn act =
690+ retryLoop $ try $ do
695+ retryLoop :: IO (Either SomeException a) -> IO a
702+ case fromException e of
703+ Just SqlError{..} | sqlState == serialization_failure
709+ -- http://www.postgresql.org/docs/current/static/errcodes-appendix.html
710+ serialization_failure = "40001"
660-- | Rollback a transaction.
712-- | Rollback a transaction.
661rollback :: Connection -> IO ()
713rollback :: Connection -> IO ()
662rollback conn = execute_ conn "ABORT" >> return ()
714rollback conn = execute_ conn "ABORT" >> return ()