hasql/library/Hasql.hs

274 lines
7.7 KiB
Haskell
Raw Normal View History

2014-10-18 00:03:02 +04:00
module Hasql
2014-10-16 19:52:49 +04:00
(
2014-10-23 18:44:50 +04:00
-- * Session
Session,
2014-10-23 21:03:05 +04:00
session,
2014-10-23 20:04:11 +04:00
2014-10-23 20:52:49 +04:00
-- ** Session Settings
SessionSettings,
sessionSettings,
2014-10-23 22:19:40 +04:00
-- ** Error
2014-10-23 20:04:11 +04:00
Error(..),
2014-10-16 19:52:49 +04:00
-- * Transaction
2014-10-23 17:58:25 +04:00
Tx,
2014-10-23 16:32:31 +04:00
Mode,
2014-10-23 18:40:31 +04:00
Backend.IsolationLevel(..),
2014-10-23 21:03:05 +04:00
tx,
2014-10-23 16:17:47 +04:00
2014-10-23 20:04:11 +04:00
-- * Statement Quasi-Quoter
QQ.q,
2014-10-23 19:58:05 +04:00
-- * Statement Execution
2014-10-23 21:03:05 +04:00
unit,
count,
single,
stream,
2014-10-23 15:31:22 +04:00
2014-10-23 20:04:11 +04:00
-- * Results Stream
TxListT,
2014-10-23 15:31:22 +04:00
2014-10-23 20:04:11 +04:00
-- * Row parser
2014-10-22 23:58:54 +04:00
RowParser.RowParser(..),
2014-10-16 19:52:49 +04:00
)
where
2014-10-23 16:32:31 +04:00
import Hasql.Prelude hiding (Error)
import Hasql.Backend (Backend)
import Hasql.RowParser (RowParser)
2014-10-18 00:03:02 +04:00
import qualified Hasql.Backend as Backend
import qualified Hasql.RowParser as RowParser
import qualified Hasql.QQ as QQ
2014-10-23 16:32:31 +04:00
import qualified ListT
import qualified Data.Pool as Pool
2014-10-16 19:52:49 +04:00
2014-10-23 20:04:11 +04:00
-- * Session
-------------------------
-- |
-- A monad transformer,
-- which executes transactions.
type Session b =
2014-10-23 22:21:15 +04:00
ReaderT (Pool.Pool (Backend.Connection b))
2014-10-23 20:04:11 +04:00
-- |
2014-10-23 20:52:49 +04:00
-- Given backend settings, session settings, and a session monad transformer,
2014-10-23 20:04:11 +04:00
-- execute it in the inner monad.
2014-10-23 21:03:05 +04:00
session ::
2014-10-23 20:04:11 +04:00
Backend.Backend b => MonadBaseControl IO m =>
2014-10-23 20:52:49 +04:00
b -> SessionSettings -> Session b m r -> m r
2014-10-23 22:21:15 +04:00
session backend (SessionSettings size timeout) reader =
2014-10-23 20:04:11 +04:00
join $ liftM restoreM $ liftBaseWith $ \runInIO ->
mask $ \unmask -> do
2014-10-23 22:21:15 +04:00
p <- Pool.createPool (Backend.connect backend) Backend.disconnect 1 timeout size
2014-10-23 22:28:56 +04:00
r <- try $ unmask $ runInIO $ runReaderT reader p
2014-10-23 22:21:15 +04:00
Pool.purgePool p
2014-10-23 22:28:56 +04:00
either onException return r
where
onException =
\case
Backend.CantConnect t -> throwIO $ CantConnect t
Backend.ConnectionLost t -> throwIO $ ConnectionLost t
2014-10-25 10:40:13 +04:00
Backend.ErroneousResult t -> throwIO $ ErroneousResult t
Backend.UnexpectedResult t -> throwIO $ UnexpectedResult t
2014-10-25 10:40:13 +04:00
Backend.UnparsableTemplate t -> throwIO $ UnparsableTemplate t
2014-10-23 22:28:56 +04:00
Backend.TransactionConflict -> $bug "Unexpected TransactionConflict exception"
Backend.NotInTransaction -> throwIO $ NotInTransaction
2014-10-23 20:04:11 +04:00
2014-10-23 20:52:49 +04:00
-- ** Session Settings
2014-10-23 16:32:31 +04:00
-------------------------
-- |
2014-10-23 20:52:49 +04:00
-- Settings of a session.
data SessionSettings =
SessionSettings !Word32 !NominalDiffTime
2014-10-23 17:56:16 +04:00
-- |
2014-10-23 20:52:49 +04:00
-- A smart constructor for session settings.
sessionSettings ::
2014-10-23 17:56:16 +04:00
Word32
-- ^
-- The maximum number of connections to keep open.
-- The smallest acceptable value is 1.
-- Requests for connections will block if this limit is reached.
->
NominalDiffTime
-- ^
-- The amount of time for which an unused connection is kept open.
-- The smallest acceptable value is 0.5 seconds.
->
2014-10-23 20:52:49 +04:00
Maybe SessionSettings
2014-10-23 17:56:16 +04:00
-- ^
2014-10-23 20:52:49 +04:00
-- Maybe session settings, if they are correct.
sessionSettings size timeout =
2014-10-23 17:56:16 +04:00
if size > 0 && timeout >= 0.5
2014-10-23 20:52:49 +04:00
then Just $ SessionSettings size timeout
2014-10-23 17:56:16 +04:00
else Nothing
2014-10-23 16:32:31 +04:00
2014-10-23 20:52:49 +04:00
2014-10-23 22:19:40 +04:00
-- ** Error
-------------------------
-- |
-- The only exception type that this API can raise.
data Error =
-- |
-- Cannot connect to a server.
CantConnect Text |
-- |
-- The connection got interrupted.
ConnectionLost Text |
-- |
2014-10-25 10:40:13 +04:00
-- An error returned from the database.
ErroneousResult Text |
-- |
2014-10-23 22:19:40 +04:00
-- Unexpected result structure.
-- Indicates usage of inappropriate statement executor.
UnexpectedResult Text |
-- |
2014-10-25 10:40:13 +04:00
-- Incorrect statement template.
UnparsableTemplate Text |
-- |
-- An operation,
-- which requires a database transaction was executed without one.
NotInTransaction |
2014-10-23 22:19:40 +04:00
-- |
2014-10-23 22:50:53 +04:00
-- Attempt to parse a row into an incompatible type.
2014-10-23 22:19:40 +04:00
-- Indicates either a mismatching schema or an incorrect query.
2014-10-23 22:50:53 +04:00
UnparsableRow Text
2014-10-23 22:19:40 +04:00
deriving (Show, Typeable)
instance Exception Error
2014-10-23 16:32:31 +04:00
-- * Transaction
-------------------------
-- |
-- A transaction specialized for backend @b@,
-- running on an anonymous state-thread @s@
-- and producing a result @r@.
2014-10-23 17:58:25 +04:00
newtype Tx b s r =
Tx (ReaderT (Backend.Connection b) IO r)
2014-10-23 16:32:31 +04:00
deriving (Functor, Applicative, Monad)
-- |
-- A transaction mode defining how a transaction should be executed.
--
-- * @Just (isolationLevel, write)@ indicates that a database transaction
-- should be established with a specified isolation level and a boolean,
-- defining, whether it would perform any modification operations.
--
-- * @Nothing@ indicates that there should be no database transaction established on
-- the backend and therefore it should be executed with no ACID guarantees,
-- but also without any induced overhead.
type Mode =
Maybe (Backend.IsolationLevel, Bool)
2014-10-23 20:52:49 +04:00
-- |
-- Execute a transaction in a session.
2014-10-23 21:03:05 +04:00
tx ::
2014-10-23 20:52:49 +04:00
Backend.Backend b => MonadBase IO m =>
Mode -> (forall s. Tx b s r) -> Session b m r
2014-10-23 21:03:05 +04:00
tx m t =
2014-10-23 22:21:15 +04:00
ReaderT $ \p -> liftBase $ Pool.withResource p $ \c -> runTx c m t
2014-10-23 16:32:31 +04:00
where
2014-10-23 20:52:49 +04:00
runTx ::
2014-10-23 16:32:31 +04:00
Backend b =>
2014-10-23 20:52:49 +04:00
Backend.Connection b -> Mode -> (forall s. Tx b s r) -> IO r
runTx connection mode (Tx reader) =
2014-10-23 22:28:56 +04:00
maybe (const id) inTransaction mode connection (runReaderT reader connection)
2014-10-23 20:52:49 +04:00
where
inTransaction ::
Backend b =>
Backend.TransactionMode -> Backend.Connection b -> IO r -> IO r
inTransaction mode c io =
do
Backend.beginTransaction mode c
try io >>= \case
Left Backend.TransactionConflict -> do
Backend.finishTransaction False c
inTransaction mode c io
Left e -> throwIO e
Right r -> do
Backend.finishTransaction True c
return r
2014-10-21 01:58:12 +04:00
2014-10-23 16:17:47 +04:00
2014-10-23 16:32:31 +04:00
-- * Results Stream
-------------------------
-- |
-- A stream of results,
-- which fetches only those that you reach.
--
-- It's a wrapper around 'ListT.ListT',
2014-10-23 19:54:40 +04:00
-- which uses the same trick as the 'ST' monad to associate with the
-- context transaction and become impossible to be used outside of it.
-- This lets the library ensure that it is safe to automatically
-- release all the resources associated with this stream.
--
-- All the functions of the \"list-t\" library are applicable to this type,
-- amongst which are 'ListT.head', 'ListT.toList', 'ListT.fold', 'ListT.traverse_'.
newtype TxListT s m r =
TxListT (ListT.ListT m r)
2014-10-23 16:32:31 +04:00
deriving (Functor, Applicative, Alternative, Monad, MonadTrans, MonadPlus,
Monoid, ListT.ListMonad)
instance ListT.ListTrans (TxListT s) where
2014-10-23 16:32:31 +04:00
uncons =
unsafeCoerce
(ListT.uncons :: ListT.ListT m r -> m (Maybe (r, ListT.ListT m r)))
2014-10-23 20:04:11 +04:00
-- * Statements execution
2014-10-23 16:32:31 +04:00
-------------------------
-- |
-- Execute a statement, which produces no result.
2014-10-23 21:03:05 +04:00
unit :: Backend b => Backend.Statement b -> Tx b s ()
unit s =
2014-10-23 17:58:25 +04:00
Tx $ ReaderT $ Backend.execute s
2014-10-23 16:32:31 +04:00
-- |
-- Execute a statement and count the amount of affected rows.
-- Useful for resolving how many rows were updated or deleted.
2014-10-23 21:03:05 +04:00
count :: Backend b => Backend.Mapping b Word64 => Backend.Statement b -> Tx b s Word64
count s =
2014-10-23 17:58:25 +04:00
Tx $ ReaderT $ Backend.executeAndCountEffects s
2014-10-23 16:32:31 +04:00
-- |
-- Execute a statement,
-- which produces a single result row:
-- a @SELECT@
-- or an @INSERT@, which produces a generated value (e.g., an auto-incremented id).
2014-10-23 21:03:05 +04:00
single :: Backend b => RowParser b r => Backend.Statement b -> Tx b s (Maybe r)
single s =
ListT.head $ stream False s
2014-10-23 16:32:31 +04:00
-- |
-- Execute a @SELECT@ statement,
-- and produce a results stream.
-- The boolean parameter specifies,
-- whether to utilize a cursor.
--
-- Cursor allows you to fetch virtually limitless results in a constant memory
-- at a cost of a small overhead.
-- Note that in most databases cursors require establishing a database transaction,
-- so a 'NotInTransaction' error will be raised if you run it improperly.
2014-10-23 21:03:05 +04:00
stream :: Backend b => RowParser b r => Bool -> Backend.Statement b -> TxListT s (Tx b s) r
stream cursor s =
do
r <- lift $ Tx $ ReaderT $ \c -> executor s c
hoistBackendStream r
where
executor =
if cursor then Backend.executeAndStreamWithCursor else Backend.executeAndStream
2014-10-25 10:40:13 +04:00
hoistBackendStream s =
TxListT $ hoist (Tx . lift) $ do
2014-10-25 10:40:13 +04:00
row <- s
2014-10-23 22:50:53 +04:00
either (lift . throwIO . UnparsableRow) return $ RowParser.parseRow row