mirror of
https://github.com/nikita-volkov/hasql.git
synced 2024-12-28 04:57:14 +03:00
New Transaction API
This commit is contained in:
parent
fec1f2ba42
commit
2508b7294f
15
Example.hs
15
Example.hs
@ -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")
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
-- |
|
-- |
|
||||||
|
189
library/HighSQL/Transaction.hs
Normal file
189
library/HighSQL/Transaction.hs
Normal 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))
|
Loading…
Reference in New Issue
Block a user