packages feed

hasql-pool 1.4.2 → 1.4.2.1

raw patch · 19 files changed

+658/−636 lines, 19 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,3 +1,9 @@+# 1.4.2.1++## Fixes++- Discard pooled connections after driver errors (#55)+ # 1.4  - Migrated to `hasql-1.10`
hasql-pool.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: hasql-pool-version: 1.4.2+version: 1.4.2.1 category: Hasql, Database, PostgreSQL synopsis: Pool of connections for Hasql homepage: https://github.com/nikita-volkov/hasql-pool@@ -68,17 +68,14 @@ library   import: base-settings   hs-source-dirs:-    src/library/exposed-    src/library/other+    src/library/ -  -- cabal-gild: discover src/library/exposed   exposed-modules:     Hasql.Pool     Hasql.Pool.Config     Hasql.Pool.Config.Defaults     Hasql.Pool.Observation -  -- cabal-gild: discover src/library/other   other-modules:     Hasql.Pool.Config.Config     Hasql.Pool.Config.Setting
src/integration-tests/Specs/BySubject/UseSpec.hs view
@@ -3,6 +3,7 @@ import Data.Text qualified as Text import Hasql.Decoders qualified as Decoders import Hasql.Encoders qualified as Encoders+import Hasql.Errors qualified as Errors import Hasql.Pool import Hasql.Session qualified as Session import Hasql.Statement qualified as Statement@@ -26,6 +27,16 @@       res <- use pool $ Sessions.selectOne       shouldSatisfy res $ isRight +  it "Driver errors cause eviction of connection" \scopeParams -> do+    settingName <- Scripts.generateVarname+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do+      use pool (Sessions.setSetting settingName "present") `shouldReturn` Right ()+      result <- use pool driverError+      result `shouldSatisfy` \case+        Left (SessionUsageError (Errors.DriverSessionError _)) -> True+        _ -> False+      use pool (Sessions.getSetting settingName) `shouldReturn` Right Nothing+   it "Connection gets returned to the pool after normal use" \scopeParams ->     Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do       _ <- use pool $ Sessions.selectOne@@ -81,3 +92,8 @@         ("select $1 :: " <> quoteIdentifier typeName)         (Encoders.param (Encoders.nonNullable (Encoders.enum Nothing typeName id)))         (Decoders.singleRow (Decoders.column (Decoders.nonNullable (Decoders.enum Nothing typeName Just))))++driverError :: Session.Session ()+driverError =+  Session.onLibpqConnection \connection ->+    pure (Left (Errors.DriverSessionError "synthetic driver error"), connection)
+ src/library/Hasql/Pool.hs view
@@ -0,0 +1,259 @@+module Hasql.Pool+  ( -- * Pool+    Pool,+    acquire,+    use,+    release,++    -- * Errors+    UsageError (..),+  )+where++import Data.UUID.V4 qualified as Uuid+import Hasql.Connection (Connection)+import Hasql.Connection qualified as Connection+import Hasql.Connection.Settings qualified as Connection.Settings+import Hasql.Errors qualified as Errors+import Hasql.Pool.Config.Config qualified as Config+import Hasql.Pool.Observation+import Hasql.Pool.Prelude+import Hasql.Pool.SessionErrorDestructors qualified as ErrorsDestruction+import Hasql.Session qualified as Session++-- | A connection tagged with metadata.+data Entry = Entry+  { entryConnection :: Connection,+    entryCreationTimeNSec :: Word64,+    entryUseTimeNSec :: Word64,+    entryId :: UUID+  }++entryIsAged :: Word64 -> Word64 -> Entry -> Bool+entryIsAged maxLifetime now Entry {..} =+  now > entryCreationTimeNSec + maxLifetime++entryIsIdle :: Word64 -> Word64 -> Entry -> Bool+entryIsIdle maxIdletime now Entry {..} =+  now > entryUseTimeNSec + maxIdletime++-- | Pool of connections to DB.+data Pool = Pool+  { -- | Pool size.+    poolSize :: Int,+    -- | Connection settings.+    poolFetchConnectionSettings :: IO Connection.Settings.Settings,+    -- | Acquisition timeout, in microseconds.+    poolAcquisitionTimeout :: Int,+    -- | Maximal connection lifetime, in nanoseconds.+    poolMaxLifetime :: Word64,+    -- | Maximal connection idle time, in nanoseconds.+    poolMaxIdletime :: Word64,+    -- | Avail connections.+    poolConnectionQueue :: TQueue Entry,+    -- | Remaining capacity.+    -- The pool size limits the sum of poolCapacity, the length+    -- of poolConnectionQueue and the number of in-flight+    -- connections.+    poolCapacity :: TVar Int,+    -- | Whether to return a connection to the pool.+    poolReuseVar :: TVar (TVar Bool),+    -- | To stop the manager thread via garbage collection.+    poolReaperRef :: IORef (),+    -- | Action for reporting the observations.+    poolObserver :: Observation -> IO (),+    -- | Initial session to execute upon every established connection.+    poolInitSession :: Session.Session ()+  }++-- | Create a connection-pool.+--+-- No connections actually get established by this function. It is delegated+-- to 'use'.+--+-- If you want to ensure that the pool connects fine at the initialization phase, just run 'use' with an empty session (@pure ()@) and check for errors.+acquire :: Config.Config -> IO Pool+acquire config = do+  connectionQueue <- newTQueueIO+  capVar <- newTVarIO (Config.size config)+  reuseVar <- newTVarIO =<< newTVarIO True+  reaperRef <- newIORef ()++  managerTid <- forkIOWithUnmask $ \unmask -> unmask $ forever $ do+    threadDelay 1000000+    now <- getMonotonicTimeNSec+    join . atomically $ do+      entries <- flushTQueue connectionQueue+      let (agedEntries, unagedEntries) = partition (entryIsAged agingTimeoutNanos now) entries+          (idleEntries, liveEntries) = partition (entryIsIdle agingTimeoutNanos now) unagedEntries+      traverse_ (writeTQueue connectionQueue) liveEntries+      return $ do+        forM_ agedEntries $ \entry -> do+          Connection.release (entryConnection entry)+          atomically $ modifyTVar' capVar succ+          (Config.observationHandler config) (ConnectionObservation (entryId entry) (TerminatedConnectionStatus AgingConnectionTerminationReason))+        forM_ idleEntries $ \entry -> do+          Connection.release (entryConnection entry)+          atomically $ modifyTVar' capVar succ+          (Config.observationHandler config) (ConnectionObservation (entryId entry) (TerminatedConnectionStatus IdlenessConnectionTerminationReason))++  void . mkWeakIORef reaperRef $ do+    -- When the pool goes out of scope, stop the manager.+    killThread managerTid++  return $ Pool (Config.size config) (Config.connectionSettingsProvider config) acqTimeoutMicros agingTimeoutNanos maxIdletimeNanos connectionQueue capVar reuseVar reaperRef (Config.observationHandler config) (Config.initSession config)+  where+    acqTimeoutMicros =+      div (fromIntegral (diffTimeToPicoseconds (Config.acquisitionTimeout config))) 1_000_000+    agingTimeoutNanos =+      div (fromIntegral (diffTimeToPicoseconds (Config.agingTimeout config))) 1_000+    maxIdletimeNanos =+      div (fromIntegral (diffTimeToPicoseconds (Config.idlenessTimeout config))) 1_000++-- | Release all the idle connections in the pool, and mark the in-use connections+-- to be released after use. Any connections acquired after the call will be+-- freshly established.+--+-- The pool remains usable after this action.+-- So you can use this function to reset the connections in the pool.+-- Naturally, you can also use it to release the resources.+release :: Pool -> IO ()+release Pool {..} =+  join . atomically $ do+    prevReuse <- readTVar poolReuseVar+    writeTVar prevReuse False+    newReuse <- newTVar True+    writeTVar poolReuseVar newReuse+    entries <- flushTQueue poolConnectionQueue+    return $ forM_ entries $ \entry -> do+      Connection.release (entryConnection entry)+      atomically $ modifyTVar' poolCapacity succ+      poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus ReleaseConnectionTerminationReason))++-- | Use a connection from the pool to run a session and return the connection+-- to the pool, when finished.+--+-- Session failing with a 'Session.ClientError' gets interpreted as a loss of+-- connection. In such case the connection does not get returned to the pool+-- and a slot gets freed up for a new connection to be established the next+-- time one is needed. The error still gets returned from this function.+--+-- __Warning:__ Due to the mechanism mentioned above you should avoid intercepting this error type from within sessions.+use :: Pool -> Session.Session a -> IO (Either UsageError a)+use Pool {..} sess = do+  timeout <- do+    delay <- registerDelay poolAcquisitionTimeout+    return $ readTVar delay+  join . atomically $ do+    reuseVar <- readTVar poolReuseVar+    asum+      [ readTQueue poolConnectionQueue <&> onConn reuseVar,+        do+          capVal <- readTVar poolCapacity+          if capVal > 0+            then do+              writeTVar poolCapacity $! pred capVal+              return $ onNewConn reuseVar+            else retry,+        do+          timedOut <- timeout+          if timedOut+            then return . return . Left $ AcquisitionTimeoutUsageError+            else retry+      ]+  where+    onNewConn reuseVar = do+      settings <- poolFetchConnectionSettings+      now <- getMonotonicTimeNSec+      id <- Uuid.nextRandom+      poolObserver (ConnectionObservation id ConnectingConnectionStatus)+      Connection.acquire settings >>= \case+        Left connErr -> do+          let connErrText = case connErr of+                Errors.NetworkingConnectionError details -> Just details+                Errors.AuthenticationConnectionError details -> Just details+                Errors.CompatibilityConnectionError details -> Just details+                Errors.OtherConnectionError details -> if details == "" then Nothing else Just details+          poolObserver (ConnectionObservation id (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason connErrText)))+          atomically $ modifyTVar' poolCapacity succ+          return $ Left $ ConnectionUsageError connErr+        Right connection -> do+          Connection.use connection poolInitSession >>= \case+            Left err -> do+              Connection.release connection+              ErrorsDestruction.reset+                ( \details -> do+                    poolObserver (ConnectionObservation id (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason (Just details))))+                )+                (poolObserver (ConnectionObservation id (TerminatedConnectionStatus (InitializationErrorTerminationReason err))))+                err+              return $ Left $ SessionUsageError err+            Right () -> do+              poolObserver (ConnectionObservation id (ReadyForUseConnectionStatus EstablishedConnectionReadyForUseReason))+              onLiveConn reuseVar (Entry connection now now id)++    onConn reuseVar entry = do+      now <- getMonotonicTimeNSec+      if entryIsAged poolMaxLifetime now entry+        then do+          Connection.release (entryConnection entry)+          poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus AgingConnectionTerminationReason))+          onNewConn reuseVar+        else+          if entryIsIdle poolMaxIdletime now entry+            then do+              Connection.release (entryConnection entry)+              poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus IdlenessConnectionTerminationReason))+              onNewConn reuseVar+            else do+              onLiveConn reuseVar entry {entryUseTimeNSec = now}++    onLiveConn reuseVar entry = do+      poolObserver (ConnectionObservation (entryId entry) InUseConnectionStatus)+      sessRes <- try @SomeException (Connection.use (entryConnection entry) sess)++      case sessRes of+        Left exc -> do+          returnConn+          throwIO exc+        Right (Left err) ->+          if ErrorsDestruction.requiresConnectionDiscard err+            then do+              Connection.release (entryConnection entry)+              atomically $ modifyTVar' poolCapacity succ+              poolObserver+                ( ConnectionObservation+                    (entryId entry)+                    (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason (ErrorsDestruction.discardDetails err)))+                )+              return $ Left $ SessionUsageError err+            else do+              returnConn+              poolObserver (ConnectionObservation (entryId entry) (ReadyForUseConnectionStatus (SessionFailedConnectionReadyForUseReason err)))+              return $ Left $ SessionUsageError err+        Right (Right res) -> do+          returnConn+          poolObserver (ConnectionObservation (entryId entry) (ReadyForUseConnectionStatus SessionSucceededConnectionReadyForUseReason))+          return $ Right res+      where+        returnConn =+          join . atomically $ do+            reuse <- readTVar reuseVar+            if reuse+              then writeTQueue poolConnectionQueue entry $> return ()+              else return $ do+                Connection.release (entryConnection entry)+                atomically $ modifyTVar' poolCapacity succ+                poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus ReleaseConnectionTerminationReason))++-- | Union over all errors that 'use' can result in.+data UsageError+  = -- | Attempt to establish a connection failed.+    ConnectionUsageError Errors.ConnectionError+  | -- | Session execution failed.+    SessionUsageError Errors.SessionError+  | -- | Timeout acquiring a connection.+    AcquisitionTimeoutUsageError+  deriving (Show, Eq)++instance Exception UsageError
+ src/library/Hasql/Pool/Config.hs view
@@ -0,0 +1,25 @@+-- | DSL for construction of configs.+module Hasql.Pool.Config+  ( Config.Config,+    settings,+    Setting.Setting,+    Setting.size,+    Setting.acquisitionTimeout,+    Setting.agingTimeout,+    Setting.idlenessTimeout,+    Setting.staticConnectionSettings,+    Setting.dynamicConnectionSettings,+    Setting.observationHandler,+    Setting.initSession,+  )+where++import Hasql.Pool.Config.Config qualified as Config+import Hasql.Pool.Config.Setting qualified as Setting+import Hasql.Pool.Prelude++-- | Compile config from a list of settings.+-- Latter settings override the preceding in cases of conflicts.+settings :: [Setting.Setting] -> Config.Config+settings =+  foldr ($) Config.defaults . fmap Setting.apply
+ src/library/Hasql/Pool/Config/Config.hs view
@@ -0,0 +1,31 @@+module Hasql.Pool.Config.Config where++import Hasql.Connection.Settings qualified as Connection.Settings+import Hasql.Pool.Config.Defaults qualified as Defaults+import Hasql.Pool.Observation (Observation)+import Hasql.Pool.Prelude+import Hasql.Session qualified as Session++-- | Configuration for Hasql connection pool.+data Config = Config+  { size :: Int,+    acquisitionTimeout :: DiffTime,+    agingTimeout :: DiffTime,+    idlenessTimeout :: DiffTime,+    connectionSettingsProvider :: IO Connection.Settings.Settings,+    observationHandler :: Observation -> IO (),+    initSession :: Session.Session ()+  }++-- | Reasonable defaults, which can be built upon.+defaults :: Config+defaults =+  Config+    { size = Defaults.size,+      acquisitionTimeout = Defaults.acquisitionTimeout,+      agingTimeout = Defaults.agingTimeout,+      idlenessTimeout = Defaults.idlenessTimeout,+      connectionSettingsProvider = Defaults.dynamicConnectionSettings,+      observationHandler = Defaults.observationHandler,+      initSession = Defaults.initSession+    }
+ src/library/Hasql/Pool/Config/Defaults.hs view
@@ -0,0 +1,47 @@+module Hasql.Pool.Config.Defaults where++import Hasql.Connection.Settings qualified as Connection.Settings+import Hasql.Pool.Observation (Observation)+import Hasql.Pool.Prelude+import Hasql.Session qualified as Session++-- |+-- 3 connections.+size :: Int+size = 3++-- |+-- 10 seconds.+acquisitionTimeout :: DiffTime+acquisitionTimeout = 10++-- |+-- 1 day.+agingTimeout :: DiffTime+agingTimeout = 60 * 60 * 24++-- |+-- 10 minutes.+idlenessTimeout :: DiffTime+idlenessTimeout = 60 * 10++-- |+-- > "postgresql://postgres:postgres@localhost:5432/postgres"+staticConnectionSettings :: Connection.Settings.Settings+staticConnectionSettings =+  "postgresql://postgres:postgres@localhost:5432/postgres"++-- |+-- > pure "postgresql://postgres:postgres@localhost:5432/postgres"+dynamicConnectionSettings :: IO Connection.Settings.Settings+dynamicConnectionSettings = pure staticConnectionSettings++-- |+-- > const (pure ())+observationHandler :: Observation -> IO ()+observationHandler = const (pure ())++-- |+-- > pure ()+initSession :: Session.Session ()+initSession = pure ()
+ src/library/Hasql/Pool/Config/Setting.hs view
@@ -0,0 +1,97 @@+module Hasql.Pool.Config.Setting where++import Hasql.Connection.Settings qualified as Connection.Settings+import Hasql.Pool.Config.Config (Config)+import Hasql.Pool.Config.Config qualified as Config+import Hasql.Pool.Observation (Observation)+import Hasql.Pool.Prelude+import Hasql.Session qualified as Session++apply :: Setting -> Config -> Config+apply (Setting run) = run++-- | A single setting of a config.+newtype Setting+  = Setting (Config -> Config)++-- | Pool size.+--+-- 3 by default.+size :: Int -> Setting+size x =+  Setting (\config -> config {Config.size = x})++-- | Connection acquisition timeout.+--+-- 10 seconds by default.+acquisitionTimeout :: DiffTime -> Setting+acquisitionTimeout x =+  Setting (\config -> config {Config.acquisitionTimeout = x})++-- | Maximal connection lifetime.+--+-- Determines how long is available for reuse.+-- After the timeout passes and an active session is finished the connection will be closed releasing a slot in the pool for a fresh connection to be established.+--+-- This is useful as a healthy measure for resetting the server-side caches.+--+-- 1 day by default.+agingTimeout :: DiffTime -> Setting+agingTimeout x =+  Setting (\config -> config {Config.agingTimeout = x})++-- | Maximal connection idle time.+--+-- How long to keep a connection open when it's not being used.+--+-- 10 minutes by default.+idlenessTimeout :: DiffTime -> Setting+idlenessTimeout x =+  Setting (\config -> config {Config.idlenessTimeout = x})++-- | Connection string.+--+-- By default it is:+--+-- > "postgresql://postgres:postgres@localhost:5432/postgres"+staticConnectionSettings :: Connection.Settings.Settings -> Setting+staticConnectionSettings x =+  Setting (\config -> config {Config.connectionSettingsProvider = pure x})++-- | Action providing connection settings.+--+-- Gets used each time a connection gets established by the pool.+-- This may be useful for some authorization models.+--+-- By default it is:+--+-- > pure "postgresql://postgres:postgres@localhost:5432/postgres"+dynamicConnectionSettings :: IO Connection.Settings.Settings -> Setting+dynamicConnectionSettings x =+  Setting (\config -> config {Config.connectionSettingsProvider = x})++-- | Observation handler.+--+-- Typically it's used for monitoring the state of the pool via metrics and logging.+--+-- If the provided action is not lightweight, it's recommended to use intermediate bufferring via channels like TBQueue to avoid occupying the pool management thread for too long.+-- E.g., if the action is @'atomically' . 'writeTBQueue' yourQueue@, then reading from it and processing can be done on a separate thread.+--+-- By default it is:+--+-- > const (pure ())+observationHandler :: (Observation -> IO ()) -> Setting+observationHandler x =+  Setting (\config -> config {Config.observationHandler = x})++-- | Initial session.+--+-- Gets executed on every connection upon acquisition.+-- Lets you specify the connection-wide settings.+--+-- E.g., you can set the search path for all the sessions executed by the pool by executing the following:+--+-- > initSession (Session.sql "SET search_path TO schema1, schema2, public;")+initSession :: Session.Session () -> Setting+initSession x =+  Setting (\config -> config {Config.initSession = x})
+ src/library/Hasql/Pool/Observation.hs view
@@ -0,0 +1,64 @@+-- | Interface for processing observations of the status of the pool.+--+-- Provides a flexible mechanism for monitoring the healthiness of the pool via logs and metrics without any opinionated choices on the actual monitoring technologies.+-- Specific interpreters are encouraged to be created as extension libraries.+module Hasql.Pool.Observation where++import Hasql.Errors qualified as Errors+import Hasql.Pool.Prelude++-- | An observation of a change of the state of a pool.+data Observation+  = -- | Status of one of the pool's connections has changed.+    ConnectionObservation+      -- | Generated connection ID.+      -- For grouping the observations by one connection.+      UUID+      -- | Status that the connection has entered.+      ConnectionStatus+  deriving (Show, Eq)++-- | Status of a connection.+--+-- <<diagrams-output/connection-status-model.png>>+data ConnectionStatus+  = -- | Connection is being established.+    --+    -- This is the initial status of every connection.+    ConnectingConnectionStatus+  | -- | Connection is established and not occupied.+    ReadyForUseConnectionStatus ConnectionReadyForUseReason+  | -- | Is being used by some session.+    --+    -- After it's done the status will transition to 'ReadyForUseConnectionStatus' or 'TerminatedConnectionStatus'.+    InUseConnectionStatus+  | -- | Connection terminated.+    TerminatedConnectionStatus ConnectionTerminationReason+  deriving (Show, Eq)++data ConnectionReadyForUseReason+  = -- | Connection just got established.+    EstablishedConnectionReadyForUseReason+  | -- | Session execution ended with a failure that does not require a connection reset.+    SessionFailedConnectionReadyForUseReason Errors.SessionError+  | -- | Session execution ended with success.+    SessionSucceededConnectionReadyForUseReason+  deriving (Show, Eq)++-- | Explanation of why a connection was terminated.+data ConnectionTerminationReason+  = -- | The age timeout of the connection has passed.+    AgingConnectionTerminationReason+  | -- | The timeout of how long a connection may remain idle in the pool has passed.+    IdlenessConnectionTerminationReason+  | -- | The connection became unusable and had to be discarded.+    --+    -- This includes connectivity issues with the server as well as fatal+    -- session errors that invalidate the connection's prepared statement or+    -- type caches.+    NetworkErrorConnectionTerminationReason (Maybe Text)+  | -- | User has invoked the 'Hasql.Pool.release' procedure.+    ReleaseConnectionTerminationReason+  | -- | Initialization session failure.+    InitializationErrorTerminationReason Errors.SessionError+  deriving (Show, Eq)
+ src/library/Hasql/Pool/Prelude.hs view
@@ -0,0 +1,75 @@+module Hasql.Pool.Prelude+  ( module Exports,+  )+where++import Control.Applicative as Exports hiding (WrappedArrow (..))+import Control.Arrow as Exports hiding (first, second)+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Concurrent.STM as Exports hiding (orElse)+import Control.Exception as Exports+import Control.Monad as Exports hiding (fail, forM, forM_, mapM, mapM_, msum, sequence, sequence_)+import Control.Monad.Fail as Exports+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.IO.Class as Exports+import Control.Monad.ST as Exports+import Data.Bifunctor as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.ByteString as Exports (ByteString)+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports hiding (toList)+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports hiding (unzip)+import Data.Functor.Compose as Exports+import Data.IORef as Exports+import Data.Int as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)+import Data.List.NonEmpty as Exports (NonEmpty (..))+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (Alt)+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.String as Exports+import Data.Text as Exports (Text)+import Data.Time as Exports+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.UUID as Exports (UUID)+import Data.Unique as Exports+import Data.Version as Exports+import Data.Void as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports+import GHC.Clock as Exports (getMonotonicTimeNSec)+import GHC.Conc as Exports hiding (orElse, threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)+import GHC.Exts as Exports (IsList (..), groupWith, inline, lazy, sortWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import Numeric as Exports+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports (Handle, hClose)+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.Printf as Exports (hPrintf, printf)+import Text.Read as Exports (Read (..), readEither, readMaybe)+import Unsafe.Coerce as Exports+import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))
+ src/library/Hasql/Pool/SessionErrorDestructors.hs view
@@ -0,0 +1,36 @@+module Hasql.Pool.SessionErrorDestructors where++import Hasql.Errors qualified as Errors+import Hasql.Pool.Prelude++reset :: (Text -> x) -> x -> Errors.SessionError -> x+reset onReset onNoReset = \case+  Errors.ConnectionSessionError details -> onReset details+  _ -> onNoReset++requiresConnectionDiscard :: Errors.SessionError -> Bool+requiresConnectionDiscard = \case+  Errors.ConnectionSessionError {} -> True+  Errors.MissingTypesSessionError {} -> True+  Errors.ScriptSessionError _ serverError -> isStaleServerError serverError+  Errors.StatementSessionError _ _ _ _ _ statementError -> statementRequiresConnectionDiscard statementError+  -- Driver errors indicate that Hasql or the server left the connection in an+  -- unexpected state. In particular, Hasql closes the libpq connection when+  -- cleanup after an interruption fails, so it must not be reused by the pool.+  Errors.DriverSessionError {} -> True++discardDetails :: Errors.SessionError -> Maybe Text+discardDetails err =+  if requiresConnectionDiscard err+    then Just $ Errors.toMessage err+    else Nothing++statementRequiresConnectionDiscard :: Errors.StatementError -> Bool+statementRequiresConnectionDiscard = \case+  Errors.ServerStatementError serverError -> isStaleServerError serverError+  Errors.UnexpectedColumnTypeStatementError {} -> True+  _ -> False++isStaleServerError :: Errors.ServerError -> Bool+isStaleServerError (Errors.ServerError code _ _ _ _) =+  code == "0A000" || code == "XX000"
− src/library/exposed/Hasql/Pool.hs
@@ -1,259 +0,0 @@-module Hasql.Pool-  ( -- * Pool-    Pool,-    acquire,-    use,-    release,--    -- * Errors-    UsageError (..),-  )-where--import Data.UUID.V4 qualified as Uuid-import Hasql.Connection (Connection)-import Hasql.Connection qualified as Connection-import Hasql.Connection.Settings qualified as Connection.Settings-import Hasql.Errors qualified as Errors-import Hasql.Pool.Config.Config qualified as Config-import Hasql.Pool.Observation-import Hasql.Pool.Prelude-import Hasql.Pool.SessionErrorDestructors qualified as ErrorsDestruction-import Hasql.Session qualified as Session---- | A connection tagged with metadata.-data Entry = Entry-  { entryConnection :: Connection,-    entryCreationTimeNSec :: Word64,-    entryUseTimeNSec :: Word64,-    entryId :: UUID-  }--entryIsAged :: Word64 -> Word64 -> Entry -> Bool-entryIsAged maxLifetime now Entry {..} =-  now > entryCreationTimeNSec + maxLifetime--entryIsIdle :: Word64 -> Word64 -> Entry -> Bool-entryIsIdle maxIdletime now Entry {..} =-  now > entryUseTimeNSec + maxIdletime---- | Pool of connections to DB.-data Pool = Pool-  { -- | Pool size.-    poolSize :: Int,-    -- | Connection settings.-    poolFetchConnectionSettings :: IO Connection.Settings.Settings,-    -- | Acquisition timeout, in microseconds.-    poolAcquisitionTimeout :: Int,-    -- | Maximal connection lifetime, in nanoseconds.-    poolMaxLifetime :: Word64,-    -- | Maximal connection idle time, in nanoseconds.-    poolMaxIdletime :: Word64,-    -- | Avail connections.-    poolConnectionQueue :: TQueue Entry,-    -- | Remaining capacity.-    -- The pool size limits the sum of poolCapacity, the length-    -- of poolConnectionQueue and the number of in-flight-    -- connections.-    poolCapacity :: TVar Int,-    -- | Whether to return a connection to the pool.-    poolReuseVar :: TVar (TVar Bool),-    -- | To stop the manager thread via garbage collection.-    poolReaperRef :: IORef (),-    -- | Action for reporting the observations.-    poolObserver :: Observation -> IO (),-    -- | Initial session to execute upon every established connection.-    poolInitSession :: Session.Session ()-  }---- | Create a connection-pool.------ No connections actually get established by this function. It is delegated--- to 'use'.------ If you want to ensure that the pool connects fine at the initialization phase, just run 'use' with an empty session (@pure ()@) and check for errors.-acquire :: Config.Config -> IO Pool-acquire config = do-  connectionQueue <- newTQueueIO-  capVar <- newTVarIO (Config.size config)-  reuseVar <- newTVarIO =<< newTVarIO True-  reaperRef <- newIORef ()--  managerTid <- forkIOWithUnmask $ \unmask -> unmask $ forever $ do-    threadDelay 1000000-    now <- getMonotonicTimeNSec-    join . atomically $ do-      entries <- flushTQueue connectionQueue-      let (agedEntries, unagedEntries) = partition (entryIsAged agingTimeoutNanos now) entries-          (idleEntries, liveEntries) = partition (entryIsIdle agingTimeoutNanos now) unagedEntries-      traverse_ (writeTQueue connectionQueue) liveEntries-      return $ do-        forM_ agedEntries $ \entry -> do-          Connection.release (entryConnection entry)-          atomically $ modifyTVar' capVar succ-          (Config.observationHandler config) (ConnectionObservation (entryId entry) (TerminatedConnectionStatus AgingConnectionTerminationReason))-        forM_ idleEntries $ \entry -> do-          Connection.release (entryConnection entry)-          atomically $ modifyTVar' capVar succ-          (Config.observationHandler config) (ConnectionObservation (entryId entry) (TerminatedConnectionStatus IdlenessConnectionTerminationReason))--  void . mkWeakIORef reaperRef $ do-    -- When the pool goes out of scope, stop the manager.-    killThread managerTid--  return $ Pool (Config.size config) (Config.connectionSettingsProvider config) acqTimeoutMicros agingTimeoutNanos maxIdletimeNanos connectionQueue capVar reuseVar reaperRef (Config.observationHandler config) (Config.initSession config)-  where-    acqTimeoutMicros =-      div (fromIntegral (diffTimeToPicoseconds (Config.acquisitionTimeout config))) 1_000_000-    agingTimeoutNanos =-      div (fromIntegral (diffTimeToPicoseconds (Config.agingTimeout config))) 1_000-    maxIdletimeNanos =-      div (fromIntegral (diffTimeToPicoseconds (Config.idlenessTimeout config))) 1_000---- | Release all the idle connections in the pool, and mark the in-use connections--- to be released after use. Any connections acquired after the call will be--- freshly established.------ The pool remains usable after this action.--- So you can use this function to reset the connections in the pool.--- Naturally, you can also use it to release the resources.-release :: Pool -> IO ()-release Pool {..} =-  join . atomically $ do-    prevReuse <- readTVar poolReuseVar-    writeTVar prevReuse False-    newReuse <- newTVar True-    writeTVar poolReuseVar newReuse-    entries <- flushTQueue poolConnectionQueue-    return $ forM_ entries $ \entry -> do-      Connection.release (entryConnection entry)-      atomically $ modifyTVar' poolCapacity succ-      poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus ReleaseConnectionTerminationReason))---- | Use a connection from the pool to run a session and return the connection--- to the pool, when finished.------ Session failing with a 'Session.ClientError' gets interpreted as a loss of--- connection. In such case the connection does not get returned to the pool--- and a slot gets freed up for a new connection to be established the next--- time one is needed. The error still gets returned from this function.------ __Warning:__ Due to the mechanism mentioned above you should avoid intercepting this error type from within sessions.-use :: Pool -> Session.Session a -> IO (Either UsageError a)-use Pool {..} sess = do-  timeout <- do-    delay <- registerDelay poolAcquisitionTimeout-    return $ readTVar delay-  join . atomically $ do-    reuseVar <- readTVar poolReuseVar-    asum-      [ readTQueue poolConnectionQueue <&> onConn reuseVar,-        do-          capVal <- readTVar poolCapacity-          if capVal > 0-            then do-              writeTVar poolCapacity $! pred capVal-              return $ onNewConn reuseVar-            else retry,-        do-          timedOut <- timeout-          if timedOut-            then return . return . Left $ AcquisitionTimeoutUsageError-            else retry-      ]-  where-    onNewConn reuseVar = do-      settings <- poolFetchConnectionSettings-      now <- getMonotonicTimeNSec-      id <- Uuid.nextRandom-      poolObserver (ConnectionObservation id ConnectingConnectionStatus)-      Connection.acquire settings >>= \case-        Left connErr -> do-          let connErrText = case connErr of-                Errors.NetworkingConnectionError details -> Just details-                Errors.AuthenticationConnectionError details -> Just details-                Errors.CompatibilityConnectionError details -> Just details-                Errors.OtherConnectionError details -> if details == "" then Nothing else Just details-          poolObserver (ConnectionObservation id (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason connErrText)))-          atomically $ modifyTVar' poolCapacity succ-          return $ Left $ ConnectionUsageError connErr-        Right connection -> do-          Connection.use connection poolInitSession >>= \case-            Left err -> do-              Connection.release connection-              ErrorsDestruction.reset-                ( \details -> do-                    poolObserver (ConnectionObservation id (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason (Just details))))-                )-                (poolObserver (ConnectionObservation id (TerminatedConnectionStatus (InitializationErrorTerminationReason err))))-                err-              return $ Left $ SessionUsageError err-            Right () -> do-              poolObserver (ConnectionObservation id (ReadyForUseConnectionStatus EstablishedConnectionReadyForUseReason))-              onLiveConn reuseVar (Entry connection now now id)--    onConn reuseVar entry = do-      now <- getMonotonicTimeNSec-      if entryIsAged poolMaxLifetime now entry-        then do-          Connection.release (entryConnection entry)-          poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus AgingConnectionTerminationReason))-          onNewConn reuseVar-        else-          if entryIsIdle poolMaxIdletime now entry-            then do-              Connection.release (entryConnection entry)-              poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus IdlenessConnectionTerminationReason))-              onNewConn reuseVar-            else do-              onLiveConn reuseVar entry {entryUseTimeNSec = now}--    onLiveConn reuseVar entry = do-      poolObserver (ConnectionObservation (entryId entry) InUseConnectionStatus)-      sessRes <- try @SomeException (Connection.use (entryConnection entry) sess)--      case sessRes of-        Left exc -> do-          returnConn-          throwIO exc-        Right (Left err) ->-          if ErrorsDestruction.requiresConnectionDiscard err-            then do-              Connection.release (entryConnection entry)-              atomically $ modifyTVar' poolCapacity succ-              poolObserver-                ( ConnectionObservation-                    (entryId entry)-                    (TerminatedConnectionStatus (NetworkErrorConnectionTerminationReason (ErrorsDestruction.discardDetails err)))-                )-              return $ Left $ SessionUsageError err-            else do-              returnConn-              poolObserver (ConnectionObservation (entryId entry) (ReadyForUseConnectionStatus (SessionFailedConnectionReadyForUseReason err)))-              return $ Left $ SessionUsageError err-        Right (Right res) -> do-          returnConn-          poolObserver (ConnectionObservation (entryId entry) (ReadyForUseConnectionStatus SessionSucceededConnectionReadyForUseReason))-          return $ Right res-      where-        returnConn =-          join . atomically $ do-            reuse <- readTVar reuseVar-            if reuse-              then writeTQueue poolConnectionQueue entry $> return ()-              else return $ do-                Connection.release (entryConnection entry)-                atomically $ modifyTVar' poolCapacity succ-                poolObserver (ConnectionObservation (entryId entry) (TerminatedConnectionStatus ReleaseConnectionTerminationReason))---- | Union over all errors that 'use' can result in.-data UsageError-  = -- | Attempt to establish a connection failed.-    ConnectionUsageError Errors.ConnectionError-  | -- | Session execution failed.-    SessionUsageError Errors.SessionError-  | -- | Timeout acquiring a connection.-    AcquisitionTimeoutUsageError-  deriving (Show, Eq)--instance Exception UsageError
− src/library/exposed/Hasql/Pool/Config.hs
@@ -1,25 +0,0 @@--- | DSL for construction of configs.-module Hasql.Pool.Config-  ( Config.Config,-    settings,-    Setting.Setting,-    Setting.size,-    Setting.acquisitionTimeout,-    Setting.agingTimeout,-    Setting.idlenessTimeout,-    Setting.staticConnectionSettings,-    Setting.dynamicConnectionSettings,-    Setting.observationHandler,-    Setting.initSession,-  )-where--import Hasql.Pool.Config.Config qualified as Config-import Hasql.Pool.Config.Setting qualified as Setting-import Hasql.Pool.Prelude---- | Compile config from a list of settings.--- Latter settings override the preceding in cases of conflicts.-settings :: [Setting.Setting] -> Config.Config-settings =-  foldr ($) Config.defaults . fmap Setting.apply
− src/library/exposed/Hasql/Pool/Config/Defaults.hs
@@ -1,47 +0,0 @@-module Hasql.Pool.Config.Defaults where--import Hasql.Connection.Settings qualified as Connection.Settings-import Hasql.Pool.Observation (Observation)-import Hasql.Pool.Prelude-import Hasql.Session qualified as Session---- |--- 3 connections.-size :: Int-size = 3---- |--- 10 seconds.-acquisitionTimeout :: DiffTime-acquisitionTimeout = 10---- |--- 1 day.-agingTimeout :: DiffTime-agingTimeout = 60 * 60 * 24---- |--- 10 minutes.-idlenessTimeout :: DiffTime-idlenessTimeout = 60 * 10---- |--- > "postgresql://postgres:postgres@localhost:5432/postgres"-staticConnectionSettings :: Connection.Settings.Settings-staticConnectionSettings =-  "postgresql://postgres:postgres@localhost:5432/postgres"---- |--- > pure "postgresql://postgres:postgres@localhost:5432/postgres"-dynamicConnectionSettings :: IO Connection.Settings.Settings-dynamicConnectionSettings = pure staticConnectionSettings---- |--- > const (pure ())-observationHandler :: Observation -> IO ()-observationHandler = const (pure ())---- |--- > pure ()-initSession :: Session.Session ()-initSession = pure ()
− src/library/exposed/Hasql/Pool/Observation.hs
@@ -1,64 +0,0 @@--- | Interface for processing observations of the status of the pool.------ Provides a flexible mechanism for monitoring the healthiness of the pool via logs and metrics without any opinionated choices on the actual monitoring technologies.--- Specific interpreters are encouraged to be created as extension libraries.-module Hasql.Pool.Observation where--import Hasql.Errors qualified as Errors-import Hasql.Pool.Prelude---- | An observation of a change of the state of a pool.-data Observation-  = -- | Status of one of the pool's connections has changed.-    ConnectionObservation-      -- | Generated connection ID.-      -- For grouping the observations by one connection.-      UUID-      -- | Status that the connection has entered.-      ConnectionStatus-  deriving (Show, Eq)---- | Status of a connection.------ <<diagrams-output/connection-status-model.png>>-data ConnectionStatus-  = -- | Connection is being established.-    ---    -- This is the initial status of every connection.-    ConnectingConnectionStatus-  | -- | Connection is established and not occupied.-    ReadyForUseConnectionStatus ConnectionReadyForUseReason-  | -- | Is being used by some session.-    ---    -- After it's done the status will transition to 'ReadyForUseConnectionStatus' or 'TerminatedConnectionStatus'.-    InUseConnectionStatus-  | -- | Connection terminated.-    TerminatedConnectionStatus ConnectionTerminationReason-  deriving (Show, Eq)--data ConnectionReadyForUseReason-  = -- | Connection just got established.-    EstablishedConnectionReadyForUseReason-  | -- | Session execution ended with a failure that does not require a connection reset.-    SessionFailedConnectionReadyForUseReason Errors.SessionError-  | -- | Session execution ended with success.-    SessionSucceededConnectionReadyForUseReason-  deriving (Show, Eq)---- | Explanation of why a connection was terminated.-data ConnectionTerminationReason-  = -- | The age timeout of the connection has passed.-    AgingConnectionTerminationReason-  | -- | The timeout of how long a connection may remain idle in the pool has passed.-    IdlenessConnectionTerminationReason-  | -- | The connection became unusable and had to be discarded.-    ---    -- This includes connectivity issues with the server as well as fatal-    -- session errors that invalidate the connection's prepared statement or-    -- type caches.-    NetworkErrorConnectionTerminationReason (Maybe Text)-  | -- | User has invoked the 'Hasql.Pool.release' procedure.-    ReleaseConnectionTerminationReason-  | -- | Initialization session failure.-    InitializationErrorTerminationReason Errors.SessionError-  deriving (Show, Eq)
− src/library/other/Hasql/Pool/Config/Config.hs
@@ -1,31 +0,0 @@-module Hasql.Pool.Config.Config where--import Hasql.Connection.Settings qualified as Connection.Settings-import Hasql.Pool.Config.Defaults qualified as Defaults-import Hasql.Pool.Observation (Observation)-import Hasql.Pool.Prelude-import Hasql.Session qualified as Session---- | Configuration for Hasql connection pool.-data Config = Config-  { size :: Int,-    acquisitionTimeout :: DiffTime,-    agingTimeout :: DiffTime,-    idlenessTimeout :: DiffTime,-    connectionSettingsProvider :: IO Connection.Settings.Settings,-    observationHandler :: Observation -> IO (),-    initSession :: Session.Session ()-  }---- | Reasonable defaults, which can be built upon.-defaults :: Config-defaults =-  Config-    { size = Defaults.size,-      acquisitionTimeout = Defaults.acquisitionTimeout,-      agingTimeout = Defaults.agingTimeout,-      idlenessTimeout = Defaults.idlenessTimeout,-      connectionSettingsProvider = Defaults.dynamicConnectionSettings,-      observationHandler = Defaults.observationHandler,-      initSession = Defaults.initSession-    }
− src/library/other/Hasql/Pool/Config/Setting.hs
@@ -1,97 +0,0 @@-module Hasql.Pool.Config.Setting where--import Hasql.Connection.Settings qualified as Connection.Settings-import Hasql.Pool.Config.Config (Config)-import Hasql.Pool.Config.Config qualified as Config-import Hasql.Pool.Observation (Observation)-import Hasql.Pool.Prelude-import Hasql.Session qualified as Session--apply :: Setting -> Config -> Config-apply (Setting run) = run---- | A single setting of a config.-newtype Setting-  = Setting (Config -> Config)---- | Pool size.------ 3 by default.-size :: Int -> Setting-size x =-  Setting (\config -> config {Config.size = x})---- | Connection acquisition timeout.------ 10 seconds by default.-acquisitionTimeout :: DiffTime -> Setting-acquisitionTimeout x =-  Setting (\config -> config {Config.acquisitionTimeout = x})---- | Maximal connection lifetime.------ Determines how long is available for reuse.--- After the timeout passes and an active session is finished the connection will be closed releasing a slot in the pool for a fresh connection to be established.------ This is useful as a healthy measure for resetting the server-side caches.------ 1 day by default.-agingTimeout :: DiffTime -> Setting-agingTimeout x =-  Setting (\config -> config {Config.agingTimeout = x})---- | Maximal connection idle time.------ How long to keep a connection open when it's not being used.------ 10 minutes by default.-idlenessTimeout :: DiffTime -> Setting-idlenessTimeout x =-  Setting (\config -> config {Config.idlenessTimeout = x})---- | Connection string.------ By default it is:------ > "postgresql://postgres:postgres@localhost:5432/postgres"-staticConnectionSettings :: Connection.Settings.Settings -> Setting-staticConnectionSettings x =-  Setting (\config -> config {Config.connectionSettingsProvider = pure x})---- | Action providing connection settings.------ Gets used each time a connection gets established by the pool.--- This may be useful for some authorization models.------ By default it is:------ > pure "postgresql://postgres:postgres@localhost:5432/postgres"-dynamicConnectionSettings :: IO Connection.Settings.Settings -> Setting-dynamicConnectionSettings x =-  Setting (\config -> config {Config.connectionSettingsProvider = x})---- | Observation handler.------ Typically it's used for monitoring the state of the pool via metrics and logging.------ If the provided action is not lightweight, it's recommended to use intermediate bufferring via channels like TBQueue to avoid occupying the pool management thread for too long.--- E.g., if the action is @'atomically' . 'writeTBQueue' yourQueue@, then reading from it and processing can be done on a separate thread.------ By default it is:------ > const (pure ())-observationHandler :: (Observation -> IO ()) -> Setting-observationHandler x =-  Setting (\config -> config {Config.observationHandler = x})---- | Initial session.------ Gets executed on every connection upon acquisition.--- Lets you specify the connection-wide settings.------ E.g., you can set the search path for all the sessions executed by the pool by executing the following:------ > initSession (Session.sql "SET search_path TO schema1, schema2, public;")-initSession :: Session.Session () -> Setting-initSession x =-  Setting (\config -> config {Config.initSession = x})
− src/library/other/Hasql/Pool/Prelude.hs
@@ -1,75 +0,0 @@-module Hasql.Pool.Prelude-  ( module Exports,-  )-where--import Control.Applicative as Exports hiding (WrappedArrow (..))-import Control.Arrow as Exports hiding (first, second)-import Control.Category as Exports-import Control.Concurrent as Exports-import Control.Concurrent.STM as Exports hiding (orElse)-import Control.Exception as Exports-import Control.Monad as Exports hiding (fail, forM, forM_, mapM, mapM_, msum, sequence, sequence_)-import Control.Monad.Fail as Exports-import Control.Monad.Fix as Exports hiding (fix)-import Control.Monad.IO.Class as Exports-import Control.Monad.ST as Exports-import Data.Bifunctor as Exports-import Data.Bits as Exports-import Data.Bool as Exports-import Data.ByteString as Exports (ByteString)-import Data.Char as Exports-import Data.Coerce as Exports-import Data.Complex as Exports-import Data.Data as Exports-import Data.Dynamic as Exports-import Data.Either as Exports-import Data.Fixed as Exports-import Data.Foldable as Exports hiding (toList)-import Data.Function as Exports hiding (id, (.))-import Data.Functor as Exports hiding (unzip)-import Data.Functor.Compose as Exports-import Data.IORef as Exports-import Data.Int as Exports-import Data.Ix as Exports-import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)-import Data.List.NonEmpty as Exports (NonEmpty (..))-import Data.Maybe as Exports-import Data.Monoid as Exports hiding (Alt)-import Data.Ord as Exports-import Data.Proxy as Exports-import Data.Ratio as Exports-import Data.STRef as Exports-import Data.String as Exports-import Data.Text as Exports (Text)-import Data.Time as Exports-import Data.Traversable as Exports-import Data.Tuple as Exports-import Data.UUID as Exports (UUID)-import Data.Unique as Exports-import Data.Version as Exports-import Data.Void as Exports-import Data.Word as Exports-import Debug.Trace as Exports-import Foreign.ForeignPtr as Exports-import Foreign.Ptr as Exports-import Foreign.StablePtr as Exports-import Foreign.Storable as Exports-import GHC.Clock as Exports (getMonotonicTimeNSec)-import GHC.Conc as Exports hiding (orElse, threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)-import GHC.Exts as Exports (IsList (..), groupWith, inline, lazy, sortWith)-import GHC.Generics as Exports (Generic)-import GHC.IO.Exception as Exports-import Numeric as Exports-import System.Environment as Exports-import System.Exit as Exports-import System.IO as Exports (Handle, hClose)-import System.IO.Error as Exports-import System.IO.Unsafe as Exports-import System.Mem as Exports-import System.Mem.StableName as Exports-import System.Timeout as Exports-import Text.Printf as Exports (hPrintf, printf)-import Text.Read as Exports (Read (..), readEither, readMaybe)-import Unsafe.Coerce as Exports-import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))
− src/library/other/Hasql/Pool/SessionErrorDestructors.hs
@@ -1,33 +0,0 @@-module Hasql.Pool.SessionErrorDestructors where--import Hasql.Errors qualified as Errors-import Hasql.Pool.Prelude--reset :: (Text -> x) -> x -> Errors.SessionError -> x-reset onReset onNoReset = \case-  Errors.ConnectionSessionError details -> onReset details-  _ -> onNoReset--requiresConnectionDiscard :: Errors.SessionError -> Bool-requiresConnectionDiscard = \case-  Errors.ConnectionSessionError {} -> True-  Errors.MissingTypesSessionError {} -> True-  Errors.ScriptSessionError _ serverError -> isStaleServerError serverError-  Errors.StatementSessionError _ _ _ _ _ statementError -> statementRequiresConnectionDiscard statementError-  Errors.DriverSessionError {} -> False--discardDetails :: Errors.SessionError -> Maybe Text-discardDetails err =-  if requiresConnectionDiscard err-    then Just $ Errors.toMessage err-    else Nothing--statementRequiresConnectionDiscard :: Errors.StatementError -> Bool-statementRequiresConnectionDiscard = \case-  Errors.ServerStatementError serverError -> isStaleServerError serverError-  Errors.UnexpectedColumnTypeStatementError {} -> True-  _ -> False--isStaleServerError :: Errors.ServerError -> Bool-isStaleServerError (Errors.ServerError code _ _ _ _) =-  code == "0A000" || code == "XX000"