{-| Module: Squeal.PostgreSQL.Session.Transaction Description: transaction control language Copyright: (c) Eitan Chatav, 2019 Maintainer: eitan@morphism.tech Stability: experimental transaction control language -} {-# LANGUAGE MonoLocalBinds , RankNTypes #-} module Squeal.PostgreSQL.Session.Transaction ( -- * Transaction Transaction , transactionally , transactionally_ , transactionallyRetry , transactionallyRetry_ , ephemerally , ephemerally_ , withSavepoint -- * Transaction Mode , TransactionMode (..) , defaultMode , longRunningMode , retryMode , IsolationLevel (..) , AccessMode (..) , DeferrableMode (..) ) where import Control.Monad.Catch import Data.ByteString import Squeal.PostgreSQL.Session.Monad import Squeal.PostgreSQL.Session.Result import Squeal.PostgreSQL.Session.Transaction.Unsafe ( TransactionMode (..) , defaultMode , longRunningMode , retryMode , IsolationLevel (..) , AccessMode (..) , DeferrableMode (..) ) import qualified Squeal.PostgreSQL.Session.Transaction.Unsafe as Unsafe {- | A type of "safe" `Transaction`s, do-blocks that permit only database operations, pure functions, and synchronous exception handling forbidding arbitrary `IO` operations. To permit arbitrary `IO`, >>> import qualified Squeal.PostgreSQL.Session.Transaction.Unsafe as Unsafe Then use the @Unsafe@ qualified form of the functions below. A safe `Transaction` can be run in two ways, 1) it can be run directly in `IO` because as a universally quantified type, @Transaction db x@ permits interpretation in "subtypes" like @(MonadPQ db m, MonadIO m, MonadCatch m) => m x@ or @PQ db db IO x@ 2) it can be run in a transaction block, using `transactionally`, `ephemerally`, or `transactionallyRetry` -} type Transaction db x = forall m. ( MonadPQ db m , MonadResult m , MonadCatch m ) => m x {- | Run a computation `transactionally`; first `Unsafe.begin`, then run the computation, `onException` `Unsafe.rollback` and rethrow the exception, otherwise `Unsafe.commit` and `return` the result. -} transactionally :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => TransactionMode -> Transaction db x -- ^ run inside a transaction -> tx x transactionally mode tx = Unsafe.transactionally mode tx -- | Run a computation `transactionally_`, in `defaultMode`. transactionally_ :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => Transaction db x -- ^ run inside a transaction -> tx x transactionally_ tx = Unsafe.transactionally_ tx {- | `transactionallyRetry` a computation; * first `Unsafe.begin`, * then `try` the computation, - if it raises a serialization failure or deadloack detection, then `Unsafe.rollback` and restart the transaction, - if it raises any other exception then `Unsafe.rollback` and rethrow the exception, - otherwise `Unsafe.commit` and `return` the result. -} transactionallyRetry :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => TransactionMode -> Transaction db x -- ^ run inside a transaction -> tx x transactionallyRetry mode tx = Unsafe.transactionallyRetry mode tx {- | `transactionallyRetry` in `retryMode`. -} transactionallyRetry_ :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => Transaction db x -- ^ run inside a transaction -> tx x transactionallyRetry_ tx = Unsafe.transactionallyRetry_ tx {- | Run a computation `ephemerally`; Like `transactionally` but always `Unsafe.rollback`, useful in testing. -} ephemerally :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => TransactionMode -> Transaction db x -- ^ run inside an ephemeral transaction -> tx x ephemerally mode tx = Unsafe.ephemerally mode tx {- | Run a computation `ephemerally` in `defaultMode`. -} ephemerally_ :: (MonadMask tx, MonadResult tx, MonadPQ db tx) => Transaction db x -- ^ run inside an ephemeral transaction -> tx x ephemerally_ tx = Unsafe.ephemerally_ tx {- | `withSavepoint`, used in a transaction block, allows a form of nested transactions, creating a savepoint, then running a transaction, rolling back to the savepoint if it returned `Left`, then releasing the savepoint and returning transaction's result. Make sure to run `withSavepoint` in a transaction block, not directly or you will provoke a SQL exception. -} withSavepoint :: ByteString -- ^ savepoint name -> Transaction db (Either e x) -> Transaction db (Either e x) withSavepoint sv tx = Unsafe.withSavepoint sv tx