{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Hyperion.Database.HasDB where
import Control.Lens (Lens', views)
import Control.Monad.Catch (MonadCatch, try)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (MonadReader)
import qualified Data.Pool as Pool
import qualified Database.SQLite.Simple as Sql
import qualified Hyperion.Log as Log
import Hyperion.ProgramId (ProgramId)
import Hyperion.Util (retryExponential)
import Prelude hiding (lookup)
data DatabaseConfig = DatabaseConfig
{ DatabaseConfig -> Pool Connection
dbPool :: Pool.Pool Sql.Connection
, DatabaseConfig -> ProgramId
dbProgramId :: ProgramId
, DatabaseConfig -> Int
dbRetries :: Int
}
class HasDB env where
dbConfigLens :: Lens' env DatabaseConfig
instance HasDB DatabaseConfig where
dbConfigLens :: (DatabaseConfig -> f DatabaseConfig)
-> DatabaseConfig -> f DatabaseConfig
dbConfigLens = (DatabaseConfig -> f DatabaseConfig)
-> DatabaseConfig -> f DatabaseConfig
forall a. a -> a
id
type Pool = Pool.Pool Sql.Connection
newDefaultPool :: FilePath -> IO (Pool.Pool Sql.Connection)
newDefaultPool :: FilePath -> IO (Pool Connection)
newDefaultPool FilePath
dbPath = do
let
stripes :: Int
stripes = Int
1
connectionTime :: NominalDiffTime
connectionTime = NominalDiffTime
5
poolSize :: Int
poolSize = Int
1
IO Connection
-> (Connection -> IO ())
-> Int
-> NominalDiffTime
-> Int
-> IO (Pool Connection)
forall a.
IO a
-> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a)
Pool.createPool (FilePath -> IO Connection
Sql.open FilePath
dbPath) Connection -> IO ()
Sql.close Int
stripes NominalDiffTime
connectionTime Int
poolSize
withConnection
:: forall m env a . (MonadIO m, MonadReader env m, HasDB env)
=> (Sql.Connection -> IO a)
-> m a
withConnection :: (Connection -> IO a) -> m a
withConnection Connection -> IO a
go = do
Pool Connection
pool <- LensLike' (Const (Pool Connection)) env DatabaseConfig
-> (DatabaseConfig -> Pool Connection) -> m (Pool Connection)
forall s (m :: * -> *) r a.
MonadReader s m =>
LensLike' (Const r) s a -> (a -> r) -> m r
views LensLike' (Const (Pool Connection)) env DatabaseConfig
forall env. HasDB env => Lens' env DatabaseConfig
dbConfigLens DatabaseConfig -> Pool Connection
dbPool
IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO a -> m a) -> IO a -> m a
forall a b. (a -> b) -> a -> b
$ Pool Connection -> (Connection -> IO a) -> IO a
forall (m :: * -> *) a b.
MonadBaseControl IO m =>
Pool a -> (a -> m b) -> m b
Pool.withResource Pool Connection
pool Connection -> IO a
go
withConnectionRetry
:: forall m env a . (MonadIO m, MonadReader env m, HasDB env, MonadCatch m)
=> (Sql.Connection -> IO a)
-> m a
withConnectionRetry :: (Connection -> IO a) -> m a
withConnectionRetry Connection -> IO a
go =
(m a -> m (Either SQLError a))
-> (WaitRetry SQLError -> m ()) -> m a -> m a
forall (m :: * -> *) a e.
MonadIO m =>
(m a -> m (Either e a)) -> (WaitRetry e -> m ()) -> m a -> m a
retryExponential (forall a.
(MonadCatch m, Exception SQLError) =>
m a -> m (Either SQLError a)
forall (m :: * -> *) e a.
(MonadCatch m, Exception e) =>
m a -> m (Either e a)
try @m @Sql.SQLError) (Text -> WaitRetry SQLError -> m ()
forall a (m :: * -> *). (Show a, MonadIO m) => Text -> a -> m ()
Log.warn Text
"Unsuccessful") ((Connection -> IO a) -> m a
forall (m :: * -> *) env a.
(MonadIO m, MonadReader env m, HasDB env) =>
(Connection -> IO a) -> m a
withConnection Connection -> IO a
go)