{-# LANGUAGE ScopedTypeVariables #-}
module Database.Persist.Sql.Run where
import Control.Exception (bracket, mask, onException)
import Control.Monad (liftM)
import Control.Monad.IO.Unlift
import qualified UnliftIO.Exception as UE
import Control.Monad.Logger.CallStack
import Control.Monad.Reader (MonadReader)
import qualified Control.Monad.Reader as MonadReader
import Control.Monad.Trans.Reader hiding (local)
import Control.Monad.Trans.Resource
import Data.Acquire (Acquire, ReleaseType(..), mkAcquireType, with)
import Data.IORef (readIORef)
import Data.Pool (Pool, LocalPool)
import Data.Pool as P
import qualified Data.Map as Map
import qualified Data.Text as T
import System.Timeout (timeout)
import Database.Persist.Class.PersistStore
import Database.Persist.Sql.Types
import Database.Persist.Sql.Types.Internal (IsolationLevel)
import Database.Persist.Sql.Raw
unsafeAcquireSqlConnFromPool
:: forall backend m
. (MonadReader (Pool backend) m, BackendCompatible SqlBackend backend)
=> m (Acquire backend)
unsafeAcquireSqlConnFromPool = do
pool <- MonadReader.ask
let freeConn :: (backend, LocalPool backend) -> ReleaseType -> IO ()
freeConn (res, localPool) relType = case relType of
ReleaseException -> P.destroyResource pool localPool res
_ -> P.putResource localPool res
return $ fst <$> mkAcquireType (P.takeResource pool) freeConn
acquireSqlConnFromPool
:: (MonadReader (Pool backend) m, BackendCompatible SqlBackend backend)
=> m (Acquire backend)
acquireSqlConnFromPool = do
connFromPool <- unsafeAcquireSqlConnFromPool
return $ connFromPool >>= acquireSqlConn
acquireSqlConnFromPoolWithIsolation
:: (MonadReader (Pool backend) m, BackendCompatible SqlBackend backend)
=> IsolationLevel -> m (Acquire backend)
acquireSqlConnFromPoolWithIsolation isolation = do
connFromPool <- unsafeAcquireSqlConnFromPool
return $ connFromPool >>= acquireSqlConnWithIsolation isolation
runSqlPool
:: (MonadUnliftIO m, BackendCompatible SqlBackend backend)
=> ReaderT backend m a -> Pool backend -> m a
runSqlPool r pconn = with (acquireSqlConnFromPool pconn) $ runReaderT r
runSqlPoolWithIsolation
:: (MonadUnliftIO m, BackendCompatible SqlBackend backend)
=> ReaderT backend m a -> Pool backend -> IsolationLevel -> m a
runSqlPoolWithIsolation r pconn i =
with (acquireSqlConnFromPoolWithIsolation i pconn) $ runReaderT r
withResourceTimeout
:: forall a m b. (MonadUnliftIO m)
=> Int
-> Pool a
-> (a -> m b)
-> m (Maybe b)
{-# SPECIALIZE withResourceTimeout :: Int -> Pool a -> (a -> IO b) -> IO (Maybe b) #-}
withResourceTimeout ms pool act = withRunInIO $ \runInIO -> mask $ \restore -> do
mres <- timeout ms $ takeResource pool
case mres of
Nothing -> runInIO $ return (Nothing :: Maybe b)
Just (resource, local) -> do
ret <- restore (runInIO (liftM Just $ act resource)) `onException`
destroyResource pool local resource
putResource local resource
return ret
{-# INLINABLE withResourceTimeout #-}
rawAcquireSqlConn
:: forall backend m
. (MonadReader backend m, BackendCompatible SqlBackend backend)
=> Maybe IsolationLevel -> m (Acquire backend)
rawAcquireSqlConn isolation = do
conn <- MonadReader.ask
let rawConn :: SqlBackend
rawConn = projectBackend conn
getter :: T.Text -> IO Statement
getter = getStmtConn rawConn
beginTransaction :: IO backend
beginTransaction = conn <$ connBegin rawConn getter isolation
finishTransaction :: backend -> ReleaseType -> IO ()
finishTransaction _ relType = case relType of
ReleaseException -> connRollback rawConn getter
_ -> connCommit rawConn getter
return $ mkAcquireType beginTransaction finishTransaction
acquireSqlConn
:: (MonadReader backend m, BackendCompatible SqlBackend backend)
=> m (Acquire backend)
acquireSqlConn = rawAcquireSqlConn Nothing
acquireSqlConnWithIsolation
:: (MonadReader backend m, BackendCompatible SqlBackend backend)
=> IsolationLevel -> m (Acquire backend)
acquireSqlConnWithIsolation = rawAcquireSqlConn . Just
runSqlConn :: (MonadUnliftIO m, BackendCompatible SqlBackend backend) => ReaderT backend m a -> backend -> m a
runSqlConn r conn = with (acquireSqlConn conn) $ runReaderT r
runSqlConnWithIsolation :: (MonadUnliftIO m, BackendCompatible SqlBackend backend) => ReaderT backend m a -> backend -> IsolationLevel -> m a
runSqlConnWithIsolation r conn isolation =
with (acquireSqlConnWithIsolation isolation conn) $ runReaderT r
runSqlPersistM
:: (BackendCompatible SqlBackend backend)
=> ReaderT backend (NoLoggingT (ResourceT IO)) a -> backend -> IO a
runSqlPersistM x conn = runResourceT $ runNoLoggingT $ runSqlConn x conn
runSqlPersistMPool
:: (BackendCompatible SqlBackend backend)
=> ReaderT backend (NoLoggingT (ResourceT IO)) a -> Pool backend -> IO a
runSqlPersistMPool x pool = runResourceT $ runNoLoggingT $ runSqlPool x pool
liftSqlPersistMPool
:: (MonadIO m, BackendCompatible SqlBackend backend)
=> ReaderT backend (NoLoggingT (ResourceT IO)) a -> Pool backend -> m a
liftSqlPersistMPool x pool = liftIO (runSqlPersistMPool x pool)
withSqlPool
:: (MonadLogger m, MonadUnliftIO m, BackendCompatible SqlBackend backend)
=> (LogFunc -> IO backend)
-> Int
-> (Pool backend -> m a)
-> m a
withSqlPool mkConn connCount f = withUnliftIO $ \u -> bracket
(unliftIO u $ createSqlPool mkConn connCount)
destroyAllResources
(unliftIO u . f)
createSqlPool
:: forall m backend. (MonadLogger m, MonadUnliftIO m, BackendCompatible SqlBackend backend)
=> (LogFunc -> IO backend)
-> Int
-> m (Pool backend)
createSqlPool mkConn size = do
logFunc <- askLogFunc
let loggedClose :: backend -> IO ()
loggedClose backend = close' backend `UE.catchAny` \e -> runLoggingT
(logError $ T.pack $ "Error closing database connection in pool: " ++ show e)
logFunc
liftIO $ createPool (mkConn logFunc) loggedClose 1 20 size
askLogFunc :: forall m. (MonadUnliftIO m, MonadLogger m) => m LogFunc
askLogFunc = withRunInIO $ \run ->
return $ \a b c d -> run (monadLoggerLog a b c d)
withSqlConn
:: (MonadUnliftIO m, MonadLogger m, BackendCompatible SqlBackend backend)
=> (LogFunc -> IO backend) -> (backend -> m a) -> m a
withSqlConn open f = do
logFunc <- askLogFunc
withRunInIO $ \run -> bracket
(open logFunc)
close'
(run . f)
close' :: (BackendCompatible SqlBackend backend) => backend -> IO ()
close' conn = do
readIORef (connStmtMap $ projectBackend conn) >>= mapM_ stmtFinalize . Map.elems
connClose $ projectBackend conn