New Transaction API

This commit is contained in:
Nikita Volkov 2014-10-14 22:59:32 +04:00
parent fec1f2ba42
commit 2508b7294f
4 changed files with 208 additions and 3 deletions

View File

@ -36,5 +36,20 @@ main =
artistID artistID
userID userID
main =
do
H.withPool $ \pool -> do
H.txIO pool (H.NoACID) $ do
H.noResultTx $
[H.q| INSERT INTO users (name, birth_date, gender) VALUES (?, ?, ?) |]
"Nikita Volkov"
"1358-10-12"
(H.Enum Male)
H.txIO pool (H.Write H.Serialized) $ do
artistID <-
H.streamWithCursorTx $
[H.q| SELECT id FROM artists WHERE name = ? |]
("Metallica")

View File

@ -41,6 +41,7 @@ library
HighSQL.Prelude HighSQL.Prelude
HighSQL.QQ.Parser HighSQL.QQ.Parser
HighSQL.RowParser HighSQL.RowParser
HighSQL.Transaction
HighSQL.API HighSQL.API
exposed-modules: exposed-modules:
HighSQL.Backend HighSQL.Backend

View File

@ -2,11 +2,11 @@
-- An open API for implementation of specific backend drivers. -- An open API for implementation of specific backend drivers.
module HighSQL.Backend where module HighSQL.Backend where
import HighSQL.Prelude import HighSQL.Prelude hiding (Error)
import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH as TH
data BackendError = data Error =
-- -- | -- -- |
-- -- The transaction failed and should be retried. -- -- The transaction failed and should be retried.
-- TransactionConflict | -- TransactionConflict |
@ -15,7 +15,7 @@ data BackendError =
ConnectionLost Text ConnectionLost Text
deriving (Show, Typeable) deriving (Show, Typeable)
instance Exception BackendError instance Exception Error
-- | -- |

View File

@ -0,0 +1,189 @@
module HighSQL.Transaction where
import HighSQL.Prelude hiding (Read, Write, Error)
import qualified HighSQL.Backend as Backend
import qualified HighSQL.RowParser as RowParser
import qualified ListT
-- |
-- A transaction with a level @l@,
-- running on an anonymous state-thread @s@
-- and gaining a result @r@.
newtype Transaction b l s r =
Transaction (ReaderT (Backend.Connection b) IO r)
deriving (Functor, Applicative, Monad)
class Level l where
run :: Backend b => Backend.Connection b -> l -> (forall s. Transaction b l s r) -> IO r
instance Level NoLocking where
run c NoLocking (Transaction r) =
handle backendHandler $ runReaderT r c
instance Level Read where
run c (Read isolation) (Transaction r) =
handle backendHandler $ Backend.inTransaction (isolation, False) (runReaderT r c) c
instance Level Write where
run c (Write isolation) (Transaction r) =
handle backendHandler $ Backend.inTransaction (isolation, True) (runReaderT r c) c
backendHandler :: Backend.Error -> IO a
backendHandler =
\case
Backend.ConnectionLost t -> throwIO $ ConnectionLost t
-- * Locking Levels
-------------------------
-- |
-- A level requiring no locking by the transaction
-- and hence providing no ACID guarantees.
-- Essentially this means that there will be no
-- traditional transaction established on the backend.
data NoLocking =
NoLocking
-- |
-- A level requiring minimal locking from the database,
-- however it only allows to execute the \"SELECT\" statements.
data Read =
Read (Backend.IsolationLevel)
-- |
-- A level, which allows to perform any kind of statements,
-- including \"SELECT\", \"UPDATE\", \"INSERT\", \"DELETE\",
-- \"CREATE\", \"DROP\" and \"ALTER\".
--
-- However, compared to 'Read',
-- it requires the database to choose
-- a more resource-demanding locking strategy.
data Write =
Write (Backend.IsolationLevel)
-- * Privileges
-------------------------
class CursorsPrivilege l
instance CursorsPrivilege Read
instance CursorsPrivilege Write
class ModificationPrivilege l
instance ModificationPrivilege Write
instance ModificationPrivilege NoLocking
-- * Results Stream
-------------------------
type ResultsStream b l s r =
TransactionListT s (Transaction b l s) r
-- |
-- A select of results,
-- which fetches only those that you reach.
--
-- It is implemented as a wrapper around 'ListT.ListT',
-- hence all the utility functions of the list transformer API
-- are applicable to this type.
--
-- It uses the same trick as 'ST' to become impossible to be
-- executed outside of its transaction.
-- Hence you can only access it while remaining in a transaction,
-- and, when the transaction finishes,
-- all the acquired resources get automatically released.
newtype TransactionListT s m r =
TransactionListT (ListT.ListT m r)
deriving (Functor, Applicative, Alternative, Monad, MonadTrans, MonadPlus,
Monoid, ListT.ListMonad)
instance ListT.ListTrans (TransactionListT s) where
uncons =
unsafeCoerce
(ListT.uncons :: ListT.ListT m r -> m (Maybe (r, ListT.ListT m r)))
-- * Error
-------------------------
-- |
-- The only exception type that this API can raise.
data Error =
-- |
-- Cannot connect to a server
-- or the connection got interrupted.
ConnectionLost Text |
-- |
-- Attempt to parse a statement execution result into an incompatible type.
-- Indicates either a mismatching schema or an incorrect query.
ResultParsingError ByteString TypeRep
deriving (Show, Typeable)
instance Exception Error
-- * Transactions
-------------------------
type Backend =
Backend.Backend
type RowParser =
RowParser.RowParser
-- |
-- Execute a modification statement producing no result.
modify ::
Backend b => ModificationPrivilege l =>
Backend.Statement b -> Transaction b l s ()
modify s =
Transaction $ ReaderT $ Backend.execute s
-- |
-- Execute a statement, which generates an auto-increment value.
modifyAndGenerate ::
Backend b => Backend.Mapping b Integer => ModificationPrivilege l =>
Backend.Statement b -> Transaction b l s (Maybe Integer)
modifyAndGenerate s =
select s >>= ListT.head
modifyAndCount ::
Backend b => Backend.Mapping b Integer => ModificationPrivilege l =>
Backend.Statement b -> Transaction b l s Integer
modifyAndCount s =
Transaction $ ReaderT $ Backend.executeAndCountEffects s
select ::
forall b l s r.
Backend b => RowParser b r => Typeable r =>
Backend.Statement b -> Transaction b l s (ResultsStream b l s r)
select s =
Transaction $ ReaderT $ \c -> do
(w, s) <- Backend.executeAndStream s c
return $ TransactionListT $ hoist (Transaction . lift) $ do
row <- replicateM w s
maybe (lift $ throwIO parsingError) return $ RowParser.parse row
where
parsingError =
ResultParsingError (fst s) (typeOf (undefined :: r))
selectWithCursor ::
forall b l s r.
Backend b => RowParser b r => Typeable r => CursorsPrivilege l =>
Backend.Statement b -> Transaction b l s (ResultsStream b l s r)
selectWithCursor s =
Transaction $ ReaderT $ \c -> do
(w, s) <- Backend.executeAndStreamWithCursor s c
return $ TransactionListT $ hoist (Transaction . lift) $ do
row <- replicateM w s
maybe (lift $ throwIO parsingError) return $ RowParser.parse row
where
parsingError =
ResultParsingError (fst s) (typeOf (undefined :: r))