packages feed

postgresql-tx 0.1.0.0 → 0.2.0.0

raw patch · 7 files changed

+379/−60 lines, 7 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Database.PostgreSQL.Tx: -- | The runtime environment needed to convert <tt>f</tt> to <a>TxM</a>.
- Database.PostgreSQL.Tx: class Tx (f :: * -> *) where {
- Database.PostgreSQL.Tx: class UnsafeTx (io :: * -> *) (t :: * -> *) | t -> io
- Database.PostgreSQL.Tx: instance (TypeError ...) => Control.Monad.IO.Class.MonadIO Database.PostgreSQL.Tx.TxM
- Database.PostgreSQL.Tx: instance Database.PostgreSQL.Tx.Tx (Control.Monad.Trans.Reader.ReaderT r Database.PostgreSQL.Tx.TxM)
- Database.PostgreSQL.Tx: instance Database.PostgreSQL.Tx.UnsafeTx GHC.Types.IO Database.PostgreSQL.Tx.TxM
- Database.PostgreSQL.Tx: instance Database.PostgreSQL.Tx.UnsafeTx io t => Database.PostgreSQL.Tx.UnsafeTx (Control.Monad.Trans.Reader.ReaderT r io) (Control.Monad.Trans.Reader.ReaderT r t)
- Database.PostgreSQL.Tx: instance GHC.Base.Applicative Database.PostgreSQL.Tx.TxM
- Database.PostgreSQL.Tx: instance GHC.Base.Functor Database.PostgreSQL.Tx.TxM
- Database.PostgreSQL.Tx: instance GHC.Base.Monad Database.PostgreSQL.Tx.TxM
- Database.PostgreSQL.Tx: tx :: Tx f => TxEnv f -> f a -> TxM a
- Database.PostgreSQL.Tx: unsafeIOTx :: UnsafeTx io t => io a -> t a
- Database.PostgreSQL.Tx: unsafeReaderIOTx :: UnsafeTx (ReaderT r io) (ReaderT r t) => (r -> io a) -> ReaderT r t a
- Database.PostgreSQL.Tx: unsafeRunIOInTxM :: IO a -> TxM a
- Database.PostgreSQL.Tx: }
+ Database.PostgreSQL.Tx: TxDeadlockDetected :: TxErrorType
+ Database.PostgreSQL.Tx: TxException :: TxErrorType -> SomeException -> TxException
+ Database.PostgreSQL.Tx: TxOtherError :: Maybe String -> TxErrorType
+ Database.PostgreSQL.Tx: TxSerializationFailure :: TxErrorType
+ Database.PostgreSQL.Tx: [cause] :: TxException -> SomeException
+ Database.PostgreSQL.Tx: [errorType] :: TxException -> TxErrorType
+ Database.PostgreSQL.Tx: askTxEnv :: TxEnv a r => TxM r a
+ Database.PostgreSQL.Tx: class TxEnv a r
+ Database.PostgreSQL.Tx: data TxErrorType
+ Database.PostgreSQL.Tx: data TxException
+ Database.PostgreSQL.Tx: lookupTxEnv :: TxEnv a r => r -> a
+ Database.PostgreSQL.Tx: mapExceptionTx :: (Exception e, Exception e') => (e -> Maybe e') -> TxM r a -> TxM r a
+ Database.PostgreSQL.Tx: shouldRetryTx :: TxException -> Bool
+ Database.PostgreSQL.Tx: throwExceptionTx :: Exception e => e -> TxM r a
+ Database.PostgreSQL.Tx.HEnv: data family HEnv (l :: [*])
+ Database.PostgreSQL.Tx.HEnv: fromTuple :: FromTuple i o => i -> HEnv o
+ Database.PostgreSQL.Tx.HEnv: infixr 2 `Cons`
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2) '[x1, x2]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3) '[x1, x2, x3]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4) '[x1, x2, x3, x4]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5) '[x1, x2, x3, x4, x5]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6) '[x1, x2, x3, x4, x5, x6]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7) '[x1, x2, x3, x4, x5, x6, x7]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8) '[x1, x2, x3, x4, x5, x6, x7, x8]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9) '[x1, x2, x3, x4, x5, x6, x7, x8, x9]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16]
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.Select a (a : xs)
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.Select a xs => Database.PostgreSQL.Tx.HEnv.Select a (x : xs)
+ Database.PostgreSQL.Tx.HEnv: instance Database.PostgreSQL.Tx.HEnv.Select a xs => Database.PostgreSQL.Tx.Internal.TxEnv a (Database.PostgreSQL.Tx.HEnv.HEnv xs)
+ Database.PostgreSQL.Tx.HEnv: singleton :: a -> HEnv '[a]
+ Database.PostgreSQL.Tx.Internal: TxDeadlockDetected :: TxErrorType
+ Database.PostgreSQL.Tx.Internal: TxException :: TxErrorType -> SomeException -> TxException
+ Database.PostgreSQL.Tx.Internal: TxOtherError :: Maybe String -> TxErrorType
+ Database.PostgreSQL.Tx.Internal: TxSerializationFailure :: TxErrorType
+ Database.PostgreSQL.Tx.Internal: UnsafeTxM :: ReaderT r IO a -> TxM r a
+ Database.PostgreSQL.Tx.Internal: [cause] :: TxException -> SomeException
+ Database.PostgreSQL.Tx.Internal: [errorType] :: TxException -> TxErrorType
+ Database.PostgreSQL.Tx.Internal: [unsafeUnTxM] :: TxM r a -> ReaderT r IO a
+ Database.PostgreSQL.Tx.Internal: askTxEnv :: TxEnv a r => TxM r a
+ Database.PostgreSQL.Tx.Internal: class TxEnv a r
+ Database.PostgreSQL.Tx.Internal: data TxErrorType
+ Database.PostgreSQL.Tx.Internal: data TxException
+ Database.PostgreSQL.Tx.Internal: fromSqlState :: Maybe String -> TxErrorType
+ Database.PostgreSQL.Tx.Internal: instance (TypeError ...) => Control.Monad.IO.Class.MonadIO (Database.PostgreSQL.Tx.Internal.TxM r)
+ Database.PostgreSQL.Tx.Internal: instance Control.Monad.Fail.MonadFail (Database.PostgreSQL.Tx.Internal.TxM r)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Base.Applicative (Database.PostgreSQL.Tx.Internal.TxM r)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Base.Functor (Database.PostgreSQL.Tx.Internal.TxM r)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Base.Monad (Database.PostgreSQL.Tx.Internal.TxM r)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Base.Monoid a => GHC.Base.Monoid (Database.PostgreSQL.Tx.Internal.TxM r a)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Base.Semigroup a => GHC.Base.Semigroup (Database.PostgreSQL.Tx.Internal.TxM r a)
+ Database.PostgreSQL.Tx.Internal: instance GHC.Classes.Eq Database.PostgreSQL.Tx.Internal.TxErrorType
+ Database.PostgreSQL.Tx.Internal: instance GHC.Exception.Type.Exception Database.PostgreSQL.Tx.Internal.TxException
+ Database.PostgreSQL.Tx.Internal: instance GHC.Show.Show Database.PostgreSQL.Tx.Internal.TxErrorType
+ Database.PostgreSQL.Tx.Internal: instance GHC.Show.Show Database.PostgreSQL.Tx.Internal.TxException
+ Database.PostgreSQL.Tx.Internal: lookupTxEnv :: TxEnv a r => r -> a
+ Database.PostgreSQL.Tx.Internal: mapExceptionTx :: (Exception e, Exception e') => (e -> Maybe e') -> TxM r a -> TxM r a
+ Database.PostgreSQL.Tx.Internal: newtype TxM r a
+ Database.PostgreSQL.Tx.Internal: shouldRetryTx :: TxException -> Bool
+ Database.PostgreSQL.Tx.Internal: throwExceptionTx :: Exception e => e -> TxM r a
+ Database.PostgreSQL.Tx.Internal: type family TxEnvs (xs :: [*]) r :: Constraint
+ Database.PostgreSQL.Tx.Internal: unsafeLookupTxEnvIO :: TxEnv a r => r -> IO a
+ Database.PostgreSQL.Tx.Internal: unsafeMkTxException :: Exception e => (e -> Maybe String) -> e -> TxException
+ Database.PostgreSQL.Tx.Internal: unsafeMkTxException' :: Exception e => (e -> TxErrorType) -> e -> TxException
+ Database.PostgreSQL.Tx.Internal: unsafeMkTxM :: (r -> IO a) -> TxM r a
+ Database.PostgreSQL.Tx.Internal: unsafeMksTxM :: TxEnv a r => (a -> IO b) -> TxM r b
+ Database.PostgreSQL.Tx.Internal: unsafeRunIOInTxM :: IO a -> TxM r a
+ Database.PostgreSQL.Tx.Internal: unsafeRunTxM :: r -> TxM r a -> IO a
+ Database.PostgreSQL.Tx.Internal: unsafeWithRunInIOTxM :: ((forall a. TxM r a -> IO a) -> IO b) -> TxM r b
+ Database.PostgreSQL.Tx.Unsafe: unsafeLookupTxEnvIO :: TxEnv a r => r -> IO a
+ Database.PostgreSQL.Tx.Unsafe: unsafeMkTxException :: Exception e => (e -> Maybe String) -> e -> TxException
+ Database.PostgreSQL.Tx.Unsafe: unsafeMkTxM :: (r -> IO a) -> TxM r a
+ Database.PostgreSQL.Tx.Unsafe: unsafeMksTxM :: TxEnv a r => (a -> IO b) -> TxM r b
+ Database.PostgreSQL.Tx.Unsafe: unsafeRunIOInTxM :: IO a -> TxM r a
+ Database.PostgreSQL.Tx.Unsafe: unsafeRunTxM :: r -> TxM r a -> IO a
+ Database.PostgreSQL.Tx.Unsafe: unsafeUnTxM :: TxM r a -> ReaderT r IO a
+ Database.PostgreSQL.Tx.Unsafe: unsafeWithRunInIOTxM :: ((forall a. TxM r a -> IO a) -> IO b) -> TxM r b
- Database.PostgreSQL.Tx: data TxM a
+ Database.PostgreSQL.Tx: data TxM r a
- Database.PostgreSQL.Tx: type family TxEnv f :: *;
+ Database.PostgreSQL.Tx: type family TxEnvs (xs :: [*]) r :: Constraint

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Change log +## 0.2.0.0++* Changed the `TxM` encoding from a plain newtype-over-IO to a reader.+* Removed the `Tx` and `UnsafeTx` type classes.+* `TxEnv` is now a type class. Added `TxEnvs` for convenience.+* Added `TxException`.+* Added the `HEnv` type.+* Changed module structure so that unsafe functions are provided in an `Unsafe` module.+ ## 0.1.0.0  Initial release
package.yaml view
@@ -1,5 +1,5 @@ name:                postgresql-tx-version:             0.1.0.0+version:             0.2.0.0 github:              "simspace/postgresql-tx" license:             BSD3 license-file:        LICENSE.md
postgresql-tx.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.31.2. -- -- see: https://github.com/sol/hpack ----- hash: e101ce5ab3c06e184533b8848069104aed53c8b9449a654bac5cb7d9ca3adced+-- hash: 6502ef6d385db187552d31216c423233d2ea504b31a19ce4f273aa8aeb6773c0  name:           postgresql-tx-version:        0.1.0.0+version:        0.2.0.0 synopsis:       A safe transaction monad for use with various PostgreSQL Haskell libraries. description:    Please see the README on GitHub at <https://github.com/simspace/postgresql-tx#readme> category:       Database@@ -32,6 +32,9 @@ library   exposed-modules:       Database.PostgreSQL.Tx+      Database.PostgreSQL.Tx.HEnv+      Database.PostgreSQL.Tx.Internal+      Database.PostgreSQL.Tx.Unsafe   other-modules:       Paths_postgresql_tx   hs-source-dirs:
src/Database/PostgreSQL/Tx.hs view
@@ -1,63 +1,40 @@ {-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-} module Database.PostgreSQL.Tx-  ( TxM(unsafeRunTxM)-  , unsafeRunIOInTxM-  , Tx(TxEnv, tx)-  , UnsafeTx(unsafeIOTx)-  , unsafeReaderIOTx-  ) where--import Control.Monad.IO.Class (MonadIO(liftIO))-import Control.Monad.Trans.Reader (ReaderT(ReaderT, runReaderT), mapReaderT)-import GHC.TypeLits (ErrorMessage(Text), TypeError)--newtype TxM a = UnsafeTxM { unsafeRunTxM :: IO a }-  deriving newtype (Functor, Applicative, Monad)--instance-  ( TypeError-      ('Text "MonadIO is banned in TxM; use 'unsafeRunIOInTxM' if you are sure this is safe IO")-  ) => MonadIO TxM-  where-  liftIO = undefined--unsafeRunIOInTxM :: IO a -> TxM a-unsafeRunIOInTxM = UnsafeTxM---- | Type class for converting an 'f' to 'TxM' given a 'TxEnv'.-class Tx (f :: * -> *) where-  -- | The runtime environment needed to convert 'f' to 'TxM'.-  type TxEnv f :: *-  -- | Converts an 'f' to a 'TxM'.-  tx :: TxEnv f -> f a -> TxM a+  ( -- * Introduction+    -- $intro -instance Tx (ReaderT r TxM) where-  type TxEnv (ReaderT r TxM) = r-  tx = flip runReaderT+    -- ** Transaction monad+    TxM --- | Promote an unsafe 'io' action to a safe 't' transaction (will be some form of 'TxM').-class UnsafeTx (io :: * -> *) (t :: * -> *) | t -> io where-  -- | Converts an 'io' action to a 't', which will be some form of 'TxM'.-  unsafeIOTx :: io a -> t a+    -- ** Transaction environment+  , TxEnv(lookupTxEnv)+  , TxEnvs+  , askTxEnv -instance UnsafeTx IO TxM where-  unsafeIOTx = unsafeRunIOInTxM+    -- ** Exceptions+  , throwExceptionTx+  , mapExceptionTx+  , TxException(..)+  , TxErrorType(..)+  , shouldRetryTx+  ) where -instance (UnsafeTx io t) => UnsafeTx (ReaderT r io) (ReaderT r t) where-  unsafeIOTx = mapReaderT unsafeIOTx+import Database.PostgreSQL.Tx.Internal --- | Promotes an unsafe 'io' function to some 'ReaderT' over 'TxM'.-unsafeReaderIOTx-  :: (UnsafeTx (ReaderT r io) (ReaderT r t))-  => (r -> io a) -> ReaderT r t a-unsafeReaderIOTx = unsafeIOTx . ReaderT+-- $intro+--+-- @postgresql-tx@ provides a transaction monad - 'TxM'. When arbitrary 'IO' is+-- attempted to be performed within 'TxM', a type error is generated. Arbitrary+-- 'IO' can still be performed when neccessary, but users must explicitly+-- opt-in to this via the "Database.PostgreSQL.Tx.Unsafe" module's @unsafe*@+-- functions.+--+-- Note that @posgresql-tx@ has no dependencies on any specific+-- @postgres@-related database libraries. This library defines the 'TxM' monad+-- and the infrastructure necessary to adapt specific database libraries for+-- 'TxM' compatiblity. The idea is that an adaptor library provides a means to+-- convert a specific database library's implementation monad into 'TxM'.+-- Application authors can then freely mix 'TxM' database functions together+-- even if the implementations of these database functions are using different+-- underlying database libraries, e.g. @postgresql-query@ , @squeal-postgresql@,+-- @postgresql-simple@, etc.
+ src/Database/PostgreSQL/Tx/HEnv.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+module Database.PostgreSQL.Tx.HEnv+  ( HEnv(Nil, Cons)+  , singleton+  , fromTuple+  ) where++import Database.PostgreSQL.Tx (TxEnv(lookupTxEnv))++-- | Glorified hlist used to construct ad hoc @tx@ runtime environments.+data family HEnv (l :: [*])+data instance HEnv '[] = Nil+data instance HEnv (x ': xs) = x `Cons` HEnv xs+infixr 2 `Cons`++-- | Construct an 'HEnv' containing a single value.+--+-- @since 0.2.0.0+singleton :: a -> HEnv '[a]+singleton = (`Cons` Nil)++-- | 'TxEnv' instance for 'HEnv'; selects the first @a@ in the 'HEnv'+-- and makes it available via the runtime environment.+--+-- @since 0.2.0.0+instance (Select a xs) => TxEnv a (HEnv xs) where+  lookupTxEnv = select++-- | Internal type class for selecting the first @a@ in an 'HEnv'.+class Select a xs where+  select :: HEnv xs -> a++instance Select a (a ': xs) where+  select (a `Cons` _) = a++instance {-# OVERLAPPABLE #-} (Select a xs) => Select a (x ': xs) where+  select (_ `Cons` xs) = select xs++-- | Internal type class for constructing an 'HEnv' from a tuple.+class FromTuple i o | i -> o where++  -- | Construct an 'HEnv' from the given tuple @i@.+  -- Instances support tuples of up to 16 elements.+  --+  -- @since 0.2.0.0+  fromTuple :: i -> HEnv o++instance FromTuple (x1, x2) '[x1, x2] where fromTuple (x1, x2) = x1 `Cons` x2 `Cons` Nil+instance FromTuple (x1, x2, x3) '[x1, x2, x3] where fromTuple (x1, x2, x3) = x1 `Cons` x2 `Cons` x3 `Cons` Nil+instance FromTuple (x1, x2, x3, x4) '[x1, x2, x3, x4] where fromTuple (x1, x2, x3, x4) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5) '[x1, x2, x3, x4, x5] where fromTuple (x1, x2, x3, x4, x5) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6) '[x1, x2, x3, x4, x5, x6] where fromTuple (x1, x2, x3, x4, x5, x6) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7) '[x1, x2, x3, x4, x5, x6, x7] where fromTuple (x1, x2, x3, x4, x5, x6, x7) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8) '[x1, x2, x3, x4, x5, x6, x7, x8] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9) '[x1, x2, x3, x4, x5, x6, x7, x8, x9] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` x12 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` x12 `Cons` x13 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` x12 `Cons` x13 `Cons` x14 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` x12 `Cons` x13 `Cons` x14 `Cons` x15 `Cons` Nil+instance FromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) '[x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16] where fromTuple (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16) = x1 `Cons` x2 `Cons` x3 `Cons` x4 `Cons` x5 `Cons` x6 `Cons` x7 `Cons` x8 `Cons` x9 `Cons` x10 `Cons` x11 `Cons` x12 `Cons` x13 `Cons` x14 `Cons` x15 `Cons` x16 `Cons` Nil+{- Instances above generated with:+import Data.List+main = do+  flip mapM_ [2..16] $ \i -> do+    let args = flip map [1..i] $ \j -> "x" ++ show j+    let commaSep = intercalate ", " args+    putStrLn $ concat+      [ "instance Mk ("+      , commaSep+      , ") '["+      , commaSep+      , "] where mk ("+      , commaSep+      , ") = "+      , intercalate " `Cons` " (args ++ ["Nil"])+      ]+-}
+ src/Database/PostgreSQL/Tx/Internal.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+module Database.PostgreSQL.Tx.Internal+  ( -- * Disclaimer+    -- $disclaimer++    -- ** Internals+    module Database.PostgreSQL.Tx.Internal+  ) where++import Control.Exception (Exception(toException), SomeException, catch, throwIO)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Reader (ReaderT(ReaderT, runReaderT))+import Data.Kind (Constraint)+import GHC.TypeLits (ErrorMessage(Text), TypeError)++-- | The transaction monad. Unifies all database integrations, regardless of+-- library, into a single monad. The @r@ type parameter represents the reader+-- environment needed for applicable database libraries. For example,+-- @postgresql-simple@ needs a @Connection@ to run its functions, so+-- its interface will require that we can obtain a @Connection@ from the @r@+-- using the 'TxEnv' type class.+--+-- @since 0.2.0.0+newtype TxM r a = UnsafeTxM+  { -- | Convert a 'TxM' action to raw 'ReaderT' over 'IO'. This is provided only to give+    -- adaptor libraries access to the underlying 'IO' that 'TxM' wraps.+    --+    -- @since 0.2.0.0+    unsafeUnTxM :: ReaderT r IO a+  } deriving newtype (Functor, Applicative, Monad, MonadFail)+    deriving (Semigroup, Monoid) via (r -> IO a)++-- | Run an 'IO' action in 'TxM'. Use this function with care - arbitrary 'IO'+-- should only be run within a transaction when truly necessary.+--+-- @since 0.2.0.0+unsafeRunIOInTxM :: IO a -> TxM r a+unsafeRunIOInTxM = UnsafeTxM . liftIO++-- | Construct a 'TxM' using a reader function. Use this function with care -+-- arbitrary 'IO' should only be run within a transaction when truly necessary.+--+-- @since 0.2.0.0+unsafeMkTxM :: (r -> IO a) -> TxM r a+unsafeMkTxM = UnsafeTxM . ReaderT++-- | Similar to 'unsafeMkTxM' but allows for constructing a 'TxM' with a+-- reader function using a specific value from the environment.+-- Use this function with care - arbitrary 'IO' should only be run+-- within a transaction when truly necessary.+--+-- @since 0.2.0.0+unsafeMksTxM :: (TxEnv a r) => (a -> IO b) -> TxM r b+unsafeMksTxM f =+  unsafeMkTxM \r -> unsafeRunTxM r do+    a <- askTxEnv+    unsafeRunIOInTxM $ f a++-- | The 'TxM' monad discourages performing arbitrary 'IO' within a+-- transaction, so this instance generates a type error when client code tries+-- to call 'liftIO'.+--+-- @since 0.1.0.0+instance+  ( TypeError+      ('Text "MonadIO is banned in TxM; use 'unsafeRunIOInTxM' if you are sure this is safe IO")+  ) => MonadIO (TxM r)+  where+  liftIO = undefined++-- | Run a 'TxM' to 'IO' given the database runtime environment @r@.+-- Use of this function outside of test suites should be rare.+--+-- @since 0.2.0.0+unsafeRunTxM :: r -> TxM r a -> IO a+unsafeRunTxM r x = runReaderT (unsafeUnTxM x) r++-- | Run a 'TxM' action in 'IO' via the provided runner function. Use this+-- function with care - arbitrary 'IO' should only be run within a transaction+-- when truly necessary.+--+-- @since 0.2.0.0+unsafeWithRunInIOTxM :: ((forall a. TxM r a -> IO a) -> IO b) -> TxM r b+unsafeWithRunInIOTxM inner = unsafeMkTxM \r -> inner (unsafeRunTxM r)++-- | A type class for specifying how to acquire an environment value+-- to be used for running an implementation of a database library.+-- For example, your database library will likely require some sort of+-- connection value to discharge its effects; in this case, you'd want to+-- define an instance of @TxEnv MyDBEnv Connection@ and use @TxM MyDBEnv@+-- as your monad for executing transactions.+--+-- Note that implementations should take care and ensure that multiple+-- instances are compatible with one another. For example, let's say you+-- have instances for both @TxEnv E PgSimple.Connection@ and+-- @TxEnv E LibPQ.Connection@; if both of these implementations are grabbing+-- connections from a pool, you will end up with each of those database+-- libraries using different connections, and thus, would be running in+-- separate transactions!+--+-- @since 0.2.0.0+class TxEnv a r where++  -- | Acquire a value @a@ via the reader environment @r@ which assists in+  -- running a 'TxM' in a transaction.+  --+  -- @since 0.2.0.0+  lookupTxEnv :: r -> a++askTxEnv :: (TxEnv a r) => TxM r a+askTxEnv = unsafeMkTxM (pure . lookupTxEnv)++-- | Analogous to 'lookupTxEnv' but can be run in 'IO' instead of 'TxM'.+--+-- @since 0.2.0.0+unsafeLookupTxEnvIO :: (TxEnv a r) => r -> IO a+unsafeLookupTxEnvIO r = unsafeRunTxM r askTxEnv++-- | Type family which allows for specifying several 'TxEnv' constraints as+-- a type-level list.+--+-- @since 0.2.0.0+type family TxEnvs (xs :: [*]) r :: Constraint where+  TxEnvs '[] r = ()+  TxEnvs (x ': xs) r = (TxEnv x r, TxEnvs xs r)++-- | Throw an exception.+--+-- @since 0.2.0.0+throwExceptionTx :: (Exception e) => e -> TxM r a+throwExceptionTx = unsafeRunIOInTxM . throwIO++-- | Catch an exception and map it to another exception type before rethrowing.+--+-- @since 0.2.0.0+mapExceptionTx+  :: (Exception e, Exception e')+  => (e -> Maybe e')+  -> TxM r a+  -> TxM r a+mapExceptionTx mapper action = do+  unsafeWithRunInIOTxM \run -> do+    catch (run action) \ex -> do+      case mapper ex of+        Nothing -> throwIO ex+        Just ex' -> throwIO ex'++data TxException = TxException+  { errorType :: TxErrorType+  , cause :: SomeException+  } deriving stock (Show)++instance Exception TxException++data TxErrorType =+    TxSerializationFailure+  | TxDeadlockDetected+  | TxOtherError (Maybe String) -- ^ PostgreSQL @errcode@, if applicable.+    deriving stock (Show, Eq)++shouldRetryTx :: TxException -> Bool+shouldRetryTx e =+  errorType e `elem`+    [ TxSerializationFailure+    , TxDeadlockDetected+    ]++fromSqlState :: Maybe String -> TxErrorType+fromSqlState = \case+  Just "40001" -> TxSerializationFailure+  Just "40P01" -> TxDeadlockDetected+  sqlState -> TxOtherError sqlState++unsafeMkTxException+  :: (Exception e) => (e -> Maybe String) -> e -> TxException+unsafeMkTxException f = unsafeMkTxException' (fromSqlState . f)++unsafeMkTxException'+  :: (Exception e)+  => (e -> TxErrorType)+  -> e -> TxException+unsafeMkTxException' f e =+  TxException+    { errorType = f e+    , cause = toException e+    }++-- $disclaimer+--+-- Changes to this module will not be reflected in the library's version+-- updates.
+ src/Database/PostgreSQL/Tx/Unsafe.hs view
@@ -0,0 +1,35 @@+module Database.PostgreSQL.Tx.Unsafe+  ( -- * Introduction+    -- $intro++    -- ** Operations+    unsafeRunIOInTxM+  , unsafeWithRunInIOTxM++    -- ** For adaptor libraries+  , unsafeUnTxM+  , unsafeRunTxM++  , unsafeMkTxM+  , unsafeMksTxM++  , unsafeLookupTxEnvIO++  , unsafeMkTxException+  ) where++import Database.PostgreSQL.Tx.Internal++-- $intro+--+-- @postgresql-tx@ discourages performing arbitrary 'IO' within a database+-- transaction, but sometimes performing this 'IO' is necessary. This module+-- provides operations and infrastructure for performing "unsafe" 'IO' actions+-- within 'TxM' or a specific database library implementation monad. It also+-- provides utilities for use in adaptor libraries.+--+-- Clients must explicitly import this module to perform 'IO' in 'TxM' or a+-- specific database library implementation monad. All functions this module+-- provides are prefixed with @unsafe*@. These two factors serve as annotation+-- to simplify understanding exactly which parts of transactional database code+-- are performing arbitary 'IO'.