module Database.PostgreSQL.PQTypes.Transaction (
Savepoint(..)
, withSavepoint
, withTransaction
, begin
, commit
, rollback
, withTransaction'
, begin'
, commit'
, rollback'
) where
import Control.Monad
import Control.Monad.Catch
import Data.Function
import Data.String
import Data.Typeable
import Data.Monoid.Utils
import Database.PostgreSQL.PQTypes.Class
import Database.PostgreSQL.PQTypes.Internal.Exception
import Database.PostgreSQL.PQTypes.SQL.Raw
import Database.PostgreSQL.PQTypes.Transaction.Settings
import Database.PostgreSQL.PQTypes.Utils
newtype Savepoint = Savepoint (RawSQL ())
instance IsString Savepoint where
fromString = Savepoint . fromString
{-# INLINABLE withSavepoint #-}
withSavepoint :: (MonadDB m, MonadMask m) => Savepoint -> m a -> m a
withSavepoint (Savepoint savepoint) m = mask $ \restore -> do
runQuery_ $ "SAVEPOINT" <+> savepoint
res <- restore m `onException` rollbackAndReleaseSavepoint
runQuery_ sqlReleaseSavepoint
return res
where
sqlReleaseSavepoint = "RELEASE SAVEPOINT" <+> savepoint
rollbackAndReleaseSavepoint = do
runQuery_ $ "ROLLBACK TO SAVEPOINT" <+> savepoint
runQuery_ sqlReleaseSavepoint
{-# INLINABLE withTransaction #-}
withTransaction :: (MonadDB m, MonadMask m) => m a -> m a
withTransaction m = getTransactionSettings >>= flip withTransaction' m
{-# INLINABLE begin #-}
begin :: MonadDB m => m ()
begin = getTransactionSettings >>= begin'
{-# INLINABLE commit #-}
commit :: MonadDB m => m ()
commit = getTransactionSettings >>= commit'
{-# INLINABLE rollback #-}
rollback :: MonadDB m => m ()
rollback = getTransactionSettings >>= rollback'
{-# INLINABLE withTransaction' #-}
withTransaction' :: (MonadDB m, MonadMask m)
=> TransactionSettings -> m a -> m a
withTransaction' ts m = mask $ \restore -> (`fix` 1) $ \loop n -> do
let maybeRestart = case tsRestartPredicate ts of
Just _ -> handleJust (expred n) (\_ -> loop $ n+1)
Nothing -> id
maybeRestart $ do
begin' ts
res <- restore m `onException` rollback' ts
commit' ts
return res
where
expred :: Integer -> SomeException -> Maybe ()
expred !n e = do
RestartPredicate f <- tsRestartPredicate ts
err <- msum [
fromException e
, fromException e >>= \DBException{..} -> cast dbeError
]
guard $ f err n
{-# INLINABLE begin' #-}
begin' :: MonadDB m => TransactionSettings -> m ()
begin' ts = runSQL_ . mintercalate " " $ ["BEGIN", isolationLevel, permissions]
where
isolationLevel = case tsIsolationLevel ts of
DefaultLevel -> ""
ReadCommitted -> "ISOLATION LEVEL READ COMMITTED"
RepeatableRead -> "ISOLATION LEVEL REPEATABLE READ"
Serializable -> "ISOLATION LEVEL SERIALIZABLE"
permissions = case tsPermissions ts of
DefaultPermissions -> ""
ReadOnly -> "READ ONLY"
ReadWrite -> "READ WRITE"
{-# INLINABLE commit' #-}
commit' :: MonadDB m => TransactionSettings -> m ()
commit' ts = do
runSQL_ "COMMIT"
when (tsAutoTransaction ts) $
begin' ts
{-# INLINABLE rollback' #-}
rollback' :: MonadDB m => TransactionSettings -> m ()
rollback' ts = do
runSQL_ "ROLLBACK"
when (tsAutoTransaction ts) $
begin' ts