@@ -123,19 +123,15 @@ import Blaze.ByteString.Builder
123 ( Builder, fromByteString, toByteString )
123 ( Builder, fromByteString, toByteString )
124import Blaze.ByteString.Builder.Char8 (fromChar)
124import Blaze.ByteString.Builder.Char8 (fromChar)
125import Control.Applicative ((<$>), pure)
125import Control.Applicative ((<$>), pure)
126−import Control.Concurrent.MVar
127import Control.Exception
126import Control.Exception
128− ( Exception, onException, throw, throwIO, finally
127+ ( Exception, throw, throwIO, finally )
129− , try, SomeException, fromException )
130import Control.Monad (foldM)
128import Control.Monad (foldM)
131import Data.ByteString (ByteString)
129import Data.ByteString (ByteString)
132import Data.Int (Int64)
130import Data.Int (Int64)
133−import qualified Data.IntMap as IntMap
134import Data.List (intersperse)
131import Data.List (intersperse)
135import Data.Monoid (mconcat)
132import Data.Monoid (mconcat)
136import Data.Typeable (Typeable)
133import Data.Typeable (Typeable)
137−import Database.PostgreSQL.Simple.BuiltinTypes ( oid2typname )
134+import Database.PostgreSQL.Simple.Compat ( (<>) )
138−import Database.PostgreSQL.Simple.Compat ( mask, (<>) )
139import Database.PostgreSQL.Simple.FromField (ResultError(..))
135import Database.PostgreSQL.Simple.FromField (ResultError(..))
140import Database.PostgreSQL.Simple.FromRow (FromRow(..))
136import Database.PostgreSQL.Simple.FromRow (FromRow(..))
141import Database.PostgreSQL.Simple.Ok
137import Database.PostgreSQL.Simple.Ok
@@ -144,6 +140,8 @@ import Database.PostgreSQL.Simple.ToRow (ToRow(..))
144import Database.PostgreSQL.Simple.Types
140import Database.PostgreSQL.Simple.Types
145 ( Binary(..), In(..), Only(..), Query(..), (:.)(..) )
141 ( Binary(..), In(..), Only(..), Query(..), (:.)(..) )
146import Database.PostgreSQL.Simple.Internal as Base
142import Database.PostgreSQL.Simple.Internal as Base
143+import Database.PostgreSQL.Simple.Transaction
144+import Database.PostgreSQL.Simple.TypeInfo
147import qualified Database.PostgreSQL.LibPQ as PQ
145import qualified Database.PostgreSQL.LibPQ as PQ
148import qualified Data.ByteString.Char8 as B
146import qualified Data.ByteString.Char8 as B
149import qualified Data.Text as T
147import qualified Data.Text as T
@@ -589,166 +587,6 @@ ellipsis bs
589 | B.length bs > 15 = B.take 10 bs `B.append` "[...]"
587 | B.length bs > 15 = B.take 10 bs `B.append` "[...]"
590 | otherwise = bs
588 | otherwise = bs
593−-- | Of the four isolation levels defined by the SQL standard,
594−-- these are the three levels distinguished by PostgreSQL as of version 9.0.
595−-- See <http://www.postgresql.org/docs/9.1/static/transaction-iso.html>
596−-- for more information. Note that prior to PostgreSQL 9.0, 'RepeatableRead'
597−-- was equivalent to 'Serializable'.
600− = DefaultIsolationLevel -- ^ the isolation level will be taken from
601− -- PostgreSQL's per-connection
602− -- @default_transaction_isolation@ variable,
603− -- which is initialized according to the
604− -- server's config. The default configuration
605− -- is 'ReadCommitted'.
609− deriving (Show, Eq, Ord, Enum, Bounded)
612− = DefaultReadWriteMode -- ^ the read-write mode will be taken from
613− -- PostgreSQL's per-connection
614− -- @default_transaction_read_only@ variable,
615− -- which is initialized according to the
616− -- server's config. The default configuration
620− deriving (Show, Eq, Ord, Enum, Bounded)
622−data TransactionMode = TransactionMode {
623− isolationLevel :: !IsolationLevel,
624− readWriteMode :: !ReadWriteMode
625− } deriving (Show, Eq)
627−defaultTransactionMode :: TransactionMode
628−defaultTransactionMode = TransactionMode
629− defaultIsolationLevel
630− defaultReadWriteMode
632−defaultIsolationLevel :: IsolationLevel
633−defaultIsolationLevel = DefaultIsolationLevel
635−defaultReadWriteMode :: ReadWriteMode
636−defaultReadWriteMode = DefaultReadWriteMode
638−-- | Execute an action inside a SQL transaction.
640−-- This function initiates a transaction with a \"@begin
641−-- transaction@\" statement, then executes the supplied action. If
642−-- the action succeeds, the transaction will be completed with
643−-- 'Base.commit' before this function returns.
645−-- If the action throws /any/ kind of exception (not just a
646−-- PostgreSQL-related exception), the transaction will be rolled back using
647−-- 'rollback', then the exception will be rethrown.
648−withTransaction :: Connection -> IO a -> IO a
649−withTransaction = withTransactionMode defaultTransactionMode
651−-- | Execute an action inside of a 'Serializable' transaction. If a
652−-- serialization failure occurs, roll back the transaction and try again.
653−-- Be warned that this may execute the IO action multiple times.
655−-- A 'Serializable' transaction creates the illusion that your program has
656−-- exclusive access to the database. This means that, even in a concurrent
657−-- setting, you can perform queries in sequence without having to worry about
658−-- what might happen between one statement and the next.
660−-- Think of it as STM, but without @retry@.
661−withTransactionSerializable :: Connection -> IO a -> IO a
662−withTransactionSerializable =
663− withTransactionModeRetry
665− { isolationLevel = Serializable
666− , readWriteMode = ReadWrite
668− retryOnNotSerializable
670− retryOnNotSerializable exception =
672− SqlError{..} | sqlState == serialization_failure
675− -- http://www.postgresql.org/docs/current/static/errcodes-appendix.html
676− serialization_failure = "40001"
678−-- | Execute an action inside a SQL transaction with a given isolation level.
679−withTransactionLevel :: IsolationLevel -> Connection -> IO a -> IO a
680−withTransactionLevel lvl
681− = withTransactionMode defaultTransactionMode { isolationLevel = lvl }
683−-- | Execute an action inside a SQL transaction with a given transaction mode.
684−withTransactionMode :: TransactionMode -> Connection -> IO a -> IO a
685−withTransactionMode mode conn act =
686− mask $ \restore -> do
688− r <- restore act `onException` rollback conn
692−-- | Like 'withTransactionMode', but also takes a custom callback to
693−-- determine if a transaction should be retried if an 'SqlError' occurs.
694−-- If the callback returns True, then the transaction will be retried.
695−-- If the callback returns False, or an exception other than an 'SqlError'
696−-- occurs then the transaction will be rolled back and the exception rethrown.
698−-- This is used to implement 'withTransactionSerializable'.
699−withTransactionModeRetry :: TransactionMode -> (SqlError -> Bool) -> Connection -> IO a -> IO a
700−withTransactionModeRetry mode shouldRetry conn act =
702− retryLoop $ try $ do
707− retryLoop :: IO (Either SomeException a) -> IO a
714− case fmap shouldRetry (fromException e) of
715− Just True -> retryLoop act'
720−-- | Rollback a transaction.
721−rollback :: Connection -> IO ()
722−rollback conn = execute_ conn "ABORT" >> return ()
724−-- | Commit a transaction.
725−commit :: Connection -> IO ()
726−commit conn = execute_ conn "COMMIT" >> return ()
728−-- | Begin a transaction.
729−begin :: Connection -> IO ()
730−begin = beginMode defaultTransactionMode
732−-- | Begin a transaction with a given isolation level
733−beginLevel :: IsolationLevel -> Connection -> IO ()
734−beginLevel lvl = beginMode defaultTransactionMode { isolationLevel = lvl }
736−-- | Begin a transaction with a given transaction mode
737−beginMode :: TransactionMode -> Connection -> IO ()
738−beginMode mode conn = do
739− _ <- execute_ conn $! Query (B.concat ["BEGIN", isolevel, readmode])
742− isolevel = case isolationLevel mode of
743− DefaultIsolationLevel -> ""
744− ReadCommitted -> " ISOLATION LEVEL READ COMMITTED"
745− RepeatableRead -> " ISOLATION LEVEL REPEATABLE READ"
746− Serializable -> " ISOLATION LEVEL SERIALIZABLE"
747− readmode = case readWriteMode mode of
748− DefaultReadWriteMode -> ""
749− ReadWrite -> " READ WRITE"
750− ReadOnly -> " READ ONLY"
752fmtError :: String -> Query -> [Action] -> a
590fmtError :: String -> Query -> [Action] -> a
753fmtError msg q xs = throw FormatError {
591fmtError msg q xs = throw FormatError {
754 fmtMessage = msg
592 fmtMessage = msg
@@ -1022,32 +860,3 @@ fmtError msg q xs = throw FormatError {
1022-- UTF-8. If you use some other encoding, decoding may fail or give
860-- UTF-8. If you use some other encoding, decoding may fail or give
1023-- wrong results. In such cases, write a @newtype@ wrapper and a
861-- wrong results. In such cases, write a @newtype@ wrapper and a
1024-- custom 'Result' instance to handle your encoding.
862-- custom 'Result' instance to handle your encoding.
1026−getTypeInfo :: Connection -> PQ.Oid -> IO TypeInfo
1027−getTypeInfo conn@Connection{..} oid =
1028− case oid2typname oid of
1029− Just name -> return $! TypeInfo { typ = NamedOid oid name
1030− , typelem = Nothing
1032− Nothing -> modifyMVar connectionObjects $ \oidmap -> do
1033− case IntMap.lookup (oid2int oid) oidmap of
1034− Just typeinfo -> return (oidmap, typeinfo)
1036− names <- query conn "SELECT p.oid, p.typname, c.oid, c.typname\
1037− \ FROM pg_type AS p LEFT OUTER JOIN pg_type AS c\
1038− \ ON c.oid = p.typelem\
1041− typinf <- case names of
1042− [] -> return $ throw (fatalError "invalid type oid")
1043− [(pOid, pTypName, mbCOid, mbCTypName)] ->
1044− return $! TypeInfo { typ = NamedOid pOid pTypName
1047− cTypName <- mbCTypName
1048− return $ NamedOid cOid cTypName
1050− _ -> fail "typename query returned more than one result"
1051− -- oid is a primary key, so the query should
1052− -- never return more than one result
1053− return (IntMap.insert (oid2int oid) typinf oidmap, typinf)