@@ -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(..)
@@ -122,7 +123,8 @@ import Blaze.ByteString.Builder.Char8 (fromChar)
122import Control.Applicative ((<$>), pure)
123import Control.Applicative ((<$>), pure)
123import Control.Concurrent.MVar
124import Control.Concurrent.MVar
124import Control.Exception
125import Control.Exception
125− ( Exception, onException, throw, throwIO, finally )
126+ ( Exception, onException, throw, throwIO, finally
127+ , try, SomeException, fromException )
126import Control.Monad (foldM)
128import Control.Monad (foldM)
127import Data.ByteString (ByteString)
129import Data.ByteString (ByteString)
128import Data.Int (Int64)
130import Data.Int (Int64)
@@ -643,6 +645,51 @@ defaultReadWriteMode = DefaultReadWriteMode
643withTransaction :: Connection -> IO a -> IO a
645withTransaction :: Connection -> IO a -> IO a
644withTransaction = withTransactionMode defaultTransactionMode
646withTransaction = withTransactionMode defaultTransactionMode
648+-- | Execute an action inside of a 'Serializable' transaction. If a
649+-- serialization failure occurs, roll back the transaction and try again.
650+-- Be warned that this may execute the IO action multiple times.
652+-- More precisely, if a 'SqlError' arises whose 'sqlState' is @\"40001\"@
653+-- (@serialization_failure@), this will issue a @ROLLBACK@, then try the action
654+-- again. If any other exception arises, this will issue a @ROLLBACK@, but
655+-- will propagate the exception instead of retrying.
657+-- A 'Serializable' transaction creates the illusion that your program has
658+-- exclusive access to the database. This means that, even in a concurrent
659+-- setting, you can perform queries in sequence without having to worry about
660+-- what might happen between one statement and the next.
662+-- Think of it as STM, but without @retry@.
663+withTransactionSerializable :: ReadWriteMode -> Connection -> IO a -> IO a
664+withTransactionSerializable readWriteMode conn act =
666+ retryLoop $ try $ do
671+ retryLoop :: IO (Either SomeException a) -> IO a
678+ case fromException e of
679+ Just SqlError{..} | sqlState == serialization_failure
685+ mode = TransactionMode
686+ { isolationLevel = Serializable
690+ -- http://www.postgresql.org/docs/current/static/errcodes-appendix.html
691+ serialization_failure = "40001"
646-- | Execute an action inside a SQL transaction with a given isolation level.
693-- | Execute an action inside a SQL transaction with a given isolation level.
647withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a
694withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a
648withTransactionLevel lvl
695withTransactionLevel lvl