module Hasql.Pool
( Pool
, Settings(..)
, UsageError(..)
, acquire
, acquireWith
, release
, use
, useWithObserver
, getPoolUsageStat
)
where
import qualified Data.Pool as ResourcePool
import System.Clock (Clock(Monotonic), diffTimeSpec, getTime, toNanoSecs)
import Hasql.Pool.Prelude
import qualified Hasql.Connection
import qualified Hasql.Session
import qualified Hasql.Pool.ResourcePool as ResourcePool
import Hasql.Pool.Observer (Observed(..), ObserverAction)
-- |
-- A pool of connections to DB.
newtype Pool =
Pool (ResourcePool.Pool (Either Hasql.Connection.ConnectionError Hasql.Connection.Connection))
deriving (Show)
type PoolSize = Int
type PoolStripes = Int
type ResidenceTimeout = NominalDiffTime
-- |
-- Connection getter action that allows for obtaining Postgres connection settings via external rsources such as AWS tokens etc.
type ConnectionGetter = IO (Either Hasql.Connection.ConnectionError Hasql.Connection.Connection)
-- |
-- Settings of the connection pool. Consist of:
--
-- * Pool-size.
--
-- * Timeout.
-- An amount of time for which an unused resource is kept open.
-- The smallest acceptable value is 0.5 seconds.
--
-- * Connection settings.
--
type Settings =
(PoolSize, ResidenceTimeout, Hasql.Connection.Settings)
-- |
-- Given the pool-size, timeout and connection settings
-- create a connection-pool.
acquire :: Settings -> IO Pool
acquire settings@(_size, _timeout, connectionSettings) =
acquireWith stripes (Hasql.Connection.acquire connectionSettings) settings
where
stripes = 1
-- |
-- Similar to 'acquire', allows for finer configuration.
acquireWith :: PoolStripes
-> ConnectionGetter
-> Settings
-> IO Pool
acquireWith stripes connGetter (size, timeout, connectionSettings) =
fmap Pool $
ResourcePool.createPool connGetter release stripes timeout size
where
release = either (const (pure ())) Hasql.Connection.release
-- |
-- Release the connection-pool.
release :: Pool -> IO ()
release (Pool pool) =
ResourcePool.destroyAllResources pool
-- |
-- A union over the connection establishment error and the session error.
data UsageError
= ConnectionError Hasql.Connection.ConnectionError
| SessionError Hasql.Session.QueryError
deriving (Show, Eq)
-- |
-- Use a connection from the pool to run a session and
-- return the connection to the pool, when finished.
use :: Pool -> Hasql.Session.Session a -> IO (Either UsageError a)
use = useWithObserver Nothing
-- |
-- Same as 'use' but allows for a custom observer action. You can use it for gathering latency metrics.
useWithObserver :: Maybe ObserverAction
-> Pool
-> Hasql.Session.Session a
-> IO (Either UsageError a)
useWithObserver observer (Pool pool) session =
fmap (either (Left . ConnectionError) (either (Left . SessionError) Right)) $
ResourcePool.withResourceOnEither pool $
traverse runQuery
where
runQuery dbConn = maybe action (runWithObserver action) observer
where
action = Hasql.Session.run session dbConn
runWithObserver action doObserve = do
let measure = getTime Monotonic
start <- measure
result <- action
end <- measure
let nsRatio = 1000000000
observed = Observed { latency = toRational (toNanoSecs (end `diffTimeSpec` start) % nsRatio)
}
doObserve observed >> pure result
getPoolStats :: Pool -> IO ResourcePool.Stats
getPoolStats (Pool p) = ResourcePool.stats p performStatsReset where
performStatsReset = False
getPoolUsageStat :: Pool -> IO PoolSize
getPoolUsageStat pool = getPoolStats pool >>= gather where
gather = pure . ResourcePool.currentUsage . ResourcePool.poolStats