hasql/library/Hasql.hs

287 lines
7.9 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 20:04:11 +04:00
-- * Error
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 =
ReaderT (Pool b)
-- |
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 21:03:05 +04:00
session backend settings reader =
2014-10-23 20:04:11 +04:00
join $ liftM restoreM $ liftBaseWith $ \runInIO ->
mask $ \unmask -> do
p <- acquirePool backend settings
r <- ($ releasePool p) $ onException $ unmask $ runInIO $ runReaderT reader p
releasePool p
return r
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
-- * Connections Pool
-------------------------
-- |
-- A connections pool.
newtype Pool b =
Pool (Pool.Pool (Backend.Connection b))
2014-10-23 16:32:31 +04:00
-- |
2014-10-23 17:56:16 +04:00
-- Initialize a pool given a backend and settings.
2014-10-23 20:52:49 +04:00
acquirePool :: Backend.Backend b => b -> SessionSettings -> IO (Pool b)
acquirePool b (SessionSettings size timeout) =
2014-10-23 17:56:16 +04:00
fmap Pool $
Pool.createPool (Backend.connect b) (Backend.disconnect) 1 timeout size
-- |
-- Release all resources of the pool.
releasePool :: Pool b -> IO ()
releasePool (Pool p) =
Pool.purgePool p
usePool :: (Backend.Connection b -> IO a) -> Pool b -> IO a
usePool f (Pool p) =
Pool.withResource p f
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 20:52:49 +04:00
ReaderT $ \p -> liftBase $ usePool (\c -> runTx c m t) p
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) =
handle backendHandler $
maybe (const id) inTransaction mode connection (runReaderT reader connection)
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
backendHandler :: Backend.Error -> IO a
backendHandler =
\case
Backend.CantConnect t -> throwIO $ CantConnect t
Backend.ConnectionLost t -> throwIO $ ConnectionLost t
Backend.UnexpectedResultStructure t -> throwIO $ UnexpectedResultStructure t
Backend.TransactionConflict -> $bug "Unexpected TransactionConflict exception"
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)))
-- * 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 |
-- |
-- Unexpected result structure.
-- Indicates usage of inappropriate statement executor.
UnexpectedResultStructure Text |
-- |
-- Attempt to parse a statement execution result into an incompatible type.
-- Indicates either a mismatching schema or an incorrect query.
ResultParsingError Text
deriving (Show, Typeable)
instance Exception Error
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.
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
hoistBackendStream (w, s) =
TxListT $ hoist (Tx . lift) $ do
row <- ($ s) $ ListT.slice $ fromMaybe ($bug "Invalid row width") $ ListT.positive w
either (lift . throwIO . ResultParsingError) return $ RowParser.parseRow row