hasql/library/Hasql.hs

381 lines
11 KiB
Haskell
Raw Normal View History

2014-10-28 15:02:44 +03:00
-- |
-- This is the API of the \"hasql\" library.
-- For an introduction to the package
-- and links to more documentation please refer to
2014-10-29 13:46:36 +03:00
-- <../ the package's index page>.
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,
sessionUnlifter,
2014-10-23 20:04:11 +04:00
2014-10-23 20:52:49 +04:00
-- ** Session Settings
SessionSettings,
sessionSettings,
2014-10-16 19:52:49 +04:00
-- * Transaction
2014-10-23 17:58:25 +04:00
Tx,
2014-10-28 17:46:12 +03:00
tx,
-- ** Transaction Settings
2014-10-23 16:32:31 +04:00
Mode,
2014-10-23 18:40:31 +04:00
Backend.IsolationLevel(..),
2014-10-23 16:17:47 +04:00
2014-10-23 20:04:11 +04:00
-- * Statement Quasi-Quoter
2014-11-11 00:06:36 +03:00
q,
2014-10-23 20:04:11 +04:00
2014-10-23 19:58:05 +04:00
-- * Statement Execution
2014-10-23 21:03:05 +04:00
unit,
count,
single,
2014-10-25 22:25:58 +04:00
list,
2014-10-23 21:03:05 +04:00
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-28 15:02:44 +03:00
-- * Error
Error(..),
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
2014-11-11 00:06:36 +03:00
import qualified Hasql.QParser as QParser
2014-10-23 16:32:31 +04:00
import qualified ListT
import qualified Data.Pool as Pool
2014-10-25 21:23:53 +04:00
import qualified Data.Vector as Vector
2014-11-11 00:06:36 +03:00
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Quote as TH
import qualified Hasql.TH as THUtil
2014-10-16 19:52:49 +04:00
2014-10-23 20:04:11 +04:00
-- * Session
-------------------------
-- |
-- A monad transformer,
-- which executes transactions.
2014-11-23 17:08:05 +03:00
--
2014-11-29 14:48:20 +03:00
-- * @b@ is a backend.
--
-- * @s@ is an anonymous variable,
-- used to associate 'sessionUnlifter' with a specific session.
--
-- * @m@ is an inner (transformed) monad.
--
-- * @r@ is a result.
newtype Session b s m r =
Session (ReaderT (Pool.Pool (Backend.Connection b)) m r)
2014-11-23 15:14:15 +03:00
deriving (Functor, Applicative, Monad, MonadTrans, MonadIO)
instance MonadTransControl (Session b s) where
newtype StT (Session b s) a = SessionStT a
liftWith onRunner =
Session $ ReaderT $ \e -> onRunner $ \(Session (ReaderT f)) -> liftM SessionStT $ f e
restoreT =
Session . ReaderT . const . liftM (\(SessionStT a) -> a)
instance (MonadBase IO m) => MonadBase IO (Session b s m) where
liftBase = Session . liftBase
instance (MonadBaseControl IO m) => MonadBaseControl IO (Session b s m) where
newtype StM (Session b s m) a = SessionStM (ComposeSt (Session b s) m a)
liftBaseWith = defaultLiftBaseWith SessionStM
restoreM = defaultRestoreM $ \(SessionStM x) -> x
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-11-29 14:48:20 +03:00
-- It uses the same trick as 'ST' with the anonymous @s@ type argument
-- to prohibit the use of the result of
-- 'sessionUnlifter' outside of its creator session.
2014-10-23 21:03:05 +04:00
session ::
2014-11-13 00:43:39 +03:00
(Backend.Backend b, MonadBaseControl IO m) =>
b -> SessionSettings -> (forall s. Session b s m r) -> m r
session backend (SessionSettings size timeout) s =
control $ \runInIO ->
2014-10-23 20:04:11 +04:00
mask $ \unmask -> do
2014-10-23 22:21:15 +04:00
p <- Pool.createPool (Backend.connect backend) Backend.disconnect 1 timeout size
r <- try $ unmask $ runInIO $ runSession p s
2014-10-23 22:21:15 +04:00
Pool.purgePool p
either (throwIO :: SomeException -> IO r) return r
-- |
-- Get a session unlifting function,
-- which allows to execute a session in the inner monad
2014-11-25 15:49:25 +03:00
-- using the resources of the current session.
--
2014-11-29 14:48:20 +03:00
-- Using this function in combination with 'lift'
-- you can interleave 'Session' with other monad transformers.
2014-11-25 15:49:25 +03:00
--
2014-11-29 14:48:20 +03:00
-- This function has the following property:
--
-- > (sessionUnlifter >>= \unlift -> lift (unlift m)) ≡ m
sessionUnlifter :: (MonadBaseControl IO m) => Session b s m (Session b s m r -> m r)
sessionUnlifter =
Session $ ReaderT $ return . runSession
runSession :: (MonadBaseControl IO m) => Pool.Pool (Backend.Connection b) -> Session b s m r -> m r
runSession e (Session r) =
control $ \runInIO ->
catch (runInIO (runReaderT r e)) $ \case
Backend.CantConnect t -> throwIO $ CantConnect t
Backend.ConnectionLost t -> throwIO $ ConnectionLost t
Backend.ErroneousResult t -> throwIO $ ErroneousResult t
Backend.UnexpectedResult t -> throwIO $ UnexpectedResult t
Backend.UnparsableTemplate t -> throwIO $ UnparsableTemplate t
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-11-10 13:48:17 +03:00
deriving (Show, Typeable, Eq, Ord)
2014-10-23 22:19:40 +04:00
instance Exception Error
2014-10-23 16:32:31 +04:00
-- * Transaction
-------------------------
-- |
2014-11-29 14:48:20 +03:00
-- A transaction specialized for a backend @b@,
-- associated with its intermediate results using an anonymous type-argument @s@ (same as in 'ST')
2014-10-23 16:32:31 +04:00
-- 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-11-29 14:48:20 +03:00
--
-- This function ensures on the type level,
-- that it's impossible to return @'TxListT' s m r@ from it.
2014-10-23 21:03:05 +04:00
tx ::
2014-11-13 00:43:39 +03:00
(Backend.Backend b, MonadBase IO m) =>
Mode -> (forall s. Tx b s r) -> Session b s m r
2014-10-23 21:03:05 +04:00
tx m t =
Session $ 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,
2014-11-29 22:23:29 +03:00
Monoid, ListT.MonadCons)
2014-10-23 16:32:31 +04:00
2014-11-29 22:23:29 +03:00
instance ListT.MonadTransUncons (TxListT s) where
2014-10-23 16:32:31 +04:00
uncons =
2014-11-29 22:23:29 +03:00
(liftM . fmap . fmap) (unsafeCoerce :: ListT.ListT m r -> TxListT s m r) .
ListT.uncons .
(unsafeCoerce :: TxListT s m r -> ListT.ListT m r)
2014-10-23 16:32:31 +04:00
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-11-13 00:43:39 +03:00
count :: (Backend b, Backend.Mapping b Word64) => Backend.Statement b -> Tx b s Word64
2014-10-23 21:03:05 +04:00
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-11-13 00:43:39 +03:00
single :: (Backend b, RowParser b r) => Backend.Statement b -> Tx b s (Maybe r)
2014-10-23 21:03:05 +04:00
single s =
2014-10-25 22:25:58 +04:00
headMay <$> list s
2014-10-23 16:32:31 +04:00
-- |
-- Execute a @SELECT@ statement,
2014-10-29 13:24:57 +03:00
-- and produce a list of results.
2014-11-13 00:43:39 +03:00
list :: (Backend b, RowParser b r) => Backend.Statement b -> Tx b s [r]
2014-10-25 22:25:58 +04:00
list s =
2014-10-25 21:23:53 +04:00
Tx $ ReaderT $ \c -> do
m <- Backend.executeAndGetMatrix s c
2014-10-25 23:46:39 +04:00
traverse (either (throwIO . UnparsableRow) return . RowParser.parseRow) $ Vector.toList m
2014-10-25 21:23:53 +04:00
-- |
-- Execute a @SELECT@ statement with a cursor,
-- and produce a results stream.
--
-- 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-11-13 00:43:39 +03:00
stream :: (Backend b, RowParser b r) => Backend.Statement b -> TxListT s (Tx b s) r
2014-10-25 21:23:53 +04:00
stream s =
do
2014-10-25 21:23:53 +04:00
s <- lift $ Tx $ ReaderT $ \c -> Backend.executeAndStream s c
TxListT $ hoist (Tx . lift) $ do
row <- s
either (lift . throwIO . UnparsableRow) return $ RowParser.parseRow row
2014-11-11 00:06:36 +03:00
-- * Statements quasi-quotation
-------------------------
-- |
-- Produces a lambda-expression,
-- which takes as many parameters as there are placeholders in the quoted text
-- and results in a 'Backend.Statement'.
--
-- E.g.:
--
2014-11-13 19:41:42 +03:00
-- >selectFive :: Statement b
2014-11-11 00:06:36 +03:00
-- >selectFive = [q|SELECT (? + ?)|] 2 3
--
q :: TH.QuasiQuoter
q =
TH.QuasiQuoter
(parseExp)
(const $ fail "Pattern context is not supported")
(const $ fail "Type context is not supported")
(const $ fail "Declaration context is not supported")
where
parseExp s =
do
n <- either (fail . showString "Parsing failure: ") return (QParser.parse (fromString s))
return $ statementF s n
statementF s n =
TH.LamE
(map TH.VarP argNames)
2014-11-18 12:19:27 +03:00
(THUtil.purify [|(,,) $(pure statementE) $(pure argsE) True|])
2014-11-11 00:06:36 +03:00
where
argNames =
map (TH.mkName . ('_' :) . show) [1 .. n]
statementE =
TH.LitE (TH.StringL s)
argsE =
TH.ListE $ flip map argNames $ \x ->
THUtil.purify
[| Backend.renderValue $(TH.varE x) |]