diff --git a/hasql-pool.cabal b/hasql-pool.cabal
--- a/hasql-pool.cabal
+++ b/hasql-pool.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: hasql-pool
-version: 1.3.0.3
+version: 1.3.0.4
 category: Hasql, Database, PostgreSQL
 synopsis: Pool of connections for Hasql
 homepage: https://github.com/nikita-volkov/hasql-pool
@@ -19,7 +19,7 @@
 
 source-repository head
   type: git
-  location: git://github.com/nikita-volkov/hasql-pool.git
+  location: https://github.com/nikita-volkov/hasql-pool
 
 common base-settings
   default-language: Haskell2010
@@ -97,9 +97,27 @@
 test-suite test
   import: base-settings
   type: exitcode-stdio-1.0
-  hs-source-dirs: src/test
+  hs-source-dirs: src/integration-tests
   main-is: Main.hs
+  other-modules:
+    Helpers.Hooks
+    Helpers.Scripts
+    Helpers.Sessions
+    Specs.BySubject.Config.AgingTimeoutSpec
+    Specs.BySubject.Config.IdlenessTimeoutSpec
+    Specs.BySubject.Config.InitSessionSpec
+    Specs.BySubject.Helpers.Sessions.CountConnectionsSpec
+    Specs.BySubject.Helpers.Sessions.GetSettingSpec
+    Specs.BySubject.ReleaseSpec
+    Specs.BySubject.SpecHook
+    Specs.BySubject.UsageError.AcquisitionTimeoutSpec
+    Specs.BySubject.UsageError.SessionSpec
+    Specs.BySubject.UseSpec
+
   ghc-options: -threaded
+  build-tool-depends:
+    hspec-discover:hspec-discover ^>=2.11.12
+
   build-depends:
     async >=2.2 && <3,
     hasql,
@@ -107,3 +125,6 @@
     hspec >=2.6 && <3,
     random >=1.2 && <2,
     rerebase >=1.15 && <2,
+    testcontainers-postgresql >=0.0.2 && <0.1,
+    text-builder >=1 && <1.1,
+    tuple ^>=0.3.0.2,
diff --git a/src/integration-tests/Helpers/Hooks.hs b/src/integration-tests/Helpers/Hooks.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Helpers/Hooks.hs
@@ -0,0 +1,19 @@
+-- | Hooks for Hspec.
+module Helpers.Hooks where
+
+import Data.Bool
+import TestcontainersPostgresql qualified
+import Prelude hiding (Handler)
+
+-- | Testing action in the scope of the host name and port of a running fresh isolated postgres server.
+type Handler = (Text, Word16) -> IO ()
+
+postgres17 :: Handler -> IO ()
+postgres17 handler =
+  TestcontainersPostgresql.run
+    TestcontainersPostgresql.Config
+      { forwardLogs = False,
+        distro = TestcontainersPostgresql.Distro17,
+        auth = TestcontainersPostgresql.TrustAuth
+      }
+    (\(host, portInt) -> handler (host, fromIntegral portInt))
diff --git a/src/integration-tests/Helpers/Scripts.hs b/src/integration-tests/Helpers/Scripts.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Helpers/Scripts.hs
@@ -0,0 +1,76 @@
+module Helpers.Scripts where
+
+import Hasql.Connection.Setting qualified as Connection.Setting
+import Hasql.Connection.Setting.Connection qualified as Connection.Setting.Connection
+import Hasql.Connection.Setting.Connection.Param qualified as Connection.Setting.Connection.Param
+import Hasql.Pool qualified as Pool
+import Hasql.Pool.Config qualified as Config
+import System.Random.Stateful qualified as Random
+import TextBuilder qualified
+import Prelude
+
+-- |
+-- Parameters provided by the scope.
+-- Host and port of a running isolated postgres server.
+type ScopeParams = (Text, Word16)
+
+onTaggedPool :: Int -> DiffTime -> DiffTime -> DiffTime -> Text -> ScopeParams -> (Pool.Pool -> IO ()) -> IO ()
+onTaggedPool poolSize acqTimeout maxLifetime maxIdletime appName (host, port) =
+  bracket
+    ( Pool.acquire
+        ( Config.settings
+            [ Config.size poolSize,
+              Config.acquisitionTimeout acqTimeout,
+              Config.agingTimeout maxLifetime,
+              Config.idlenessTimeout maxIdletime,
+              Config.staticConnectionSettings
+                [ Connection.Setting.connection
+                    ( Connection.Setting.Connection.params
+                        [ Connection.Setting.Connection.Param.host host,
+                          Connection.Setting.Connection.Param.port (fromIntegral port),
+                          Connection.Setting.Connection.Param.user "postgres",
+                          Connection.Setting.Connection.Param.password "",
+                          Connection.Setting.Connection.Param.dbname "postgres",
+                          Connection.Setting.Connection.Param.other "application_name" appName
+                        ]
+                    )
+                ]
+            ]
+        )
+    )
+    Pool.release
+
+onAutotaggedPool :: Int -> DiffTime -> DiffTime -> DiffTime -> ScopeParams -> (Text -> Pool.Pool -> IO ()) -> IO ()
+onAutotaggedPool poolSize acqTimeout maxLifetime maxIdletime (host, port) cont = do
+  -- Generate app name
+  appName <- generateName "hasql-pool-test-"
+  onTaggedPool poolSize acqTimeout maxLifetime maxIdletime appName (host, port) (cont appName)
+
+onDefaultTaggedPool :: ScopeParams -> (Text -> Pool.Pool -> IO ()) -> IO ()
+onDefaultTaggedPool =
+  onAutotaggedPool 3 10 1_800 1_800
+
+generateName :: Text -> IO Text
+generateName prefix = do
+  uniqueNum1 <- Random.uniformWord64 Random.globalStdGen
+  uniqueNum2 <- Random.uniformWord64 Random.globalStdGen
+  pure
+    $ TextBuilder.toText
+    $ mconcat
+    $ [ TextBuilder.text prefix,
+        TextBuilder.decimal uniqueNum1,
+        TextBuilder.decimal uniqueNum2
+      ]
+
+generateVarname :: IO Text
+generateVarname = do
+  uniqueNum1 <- Random.uniformWord64 Random.globalStdGen
+  uniqueNum2 <- Random.uniformWord64 Random.globalStdGen
+  pure
+    $ TextBuilder.toText
+    $ mconcat
+    $ [ "testing.v",
+        TextBuilder.decimal uniqueNum1,
+        "v",
+        TextBuilder.decimal uniqueNum2
+      ]
diff --git a/src/integration-tests/Helpers/Sessions.hs b/src/integration-tests/Helpers/Sessions.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Helpers/Sessions.hs
@@ -0,0 +1,64 @@
+module Helpers.Sessions
+  ( selectOne,
+    badQuery,
+    closeConn,
+    setSetting,
+    getSetting,
+    countConnections,
+  )
+where
+
+import Data.Tuple.All
+import Hasql.Connection qualified as Connection
+import Hasql.Decoders qualified as Decoders
+import Hasql.Encoders qualified as Encoders
+import Hasql.Session qualified as Session
+import Hasql.Statement qualified as Statement
+import Prelude
+
+selectOne :: Session.Session Int64
+selectOne =
+  Session.statement () statement
+  where
+    statement = Statement.Statement "SELECT 1" Encoders.noParams decoder True
+    decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8))
+
+badQuery :: Session.Session ()
+badQuery =
+  Session.statement () statement
+  where
+    statement =
+      Statement.Statement "zzz" Encoders.noParams Decoders.noResult True
+
+closeConn :: Session.Session ()
+closeConn = do
+  conn <- ask
+  liftIO $ Connection.release conn
+
+setSetting :: Text -> Text -> Session.Session ()
+setSetting name value = do
+  Session.statement (name, value) statement
+  where
+    statement =
+      Statement.Statement "SELECT set_config($1, $2, false)" encoder Decoders.noResult True
+    encoder =
+      mconcat
+        [ sel1 >$< Encoders.param (Encoders.nonNullable Encoders.text),
+          sel2 >$< Encoders.param (Encoders.nonNullable Encoders.text)
+        ]
+
+getSetting :: Text -> Session.Session (Maybe Text)
+getSetting name = do
+  Session.statement name statement
+  where
+    statement = Statement.Statement "SELECT current_setting($1, true)" encoder decoder True
+    encoder = Encoders.param (Encoders.nonNullable Encoders.text)
+    decoder = Decoders.singleRow (Decoders.column (Decoders.nullable Decoders.text))
+
+countConnections :: Text -> Session.Session Int64
+countConnections appName = do
+  Session.statement appName statement
+  where
+    statement = Statement.Statement "SELECT count(*) FROM pg_stat_activity WHERE application_name = $1" encoder decoder True
+    encoder = Encoders.param (Encoders.nonNullable Encoders.text)
+    decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8))
diff --git a/src/integration-tests/Main.hs b/src/integration-tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Main.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/src/integration-tests/Specs/BySubject/Config/AgingTimeoutSpec.hs b/src/integration-tests/Specs/BySubject/Config/AgingTimeoutSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/Config/AgingTimeoutSpec.hs
@@ -0,0 +1,32 @@
+module Specs.BySubject.Config.AgingTimeoutSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Actively times out old connections" \scopeParams -> do
+    Scripts.onAutotaggedPool 3 10 1_800 1_800 scopeParams \_appName1 pool1 -> do
+      Scripts.onAutotaggedPool 3 10 0.5 1_800 scopeParams \appName2 pool2 -> do
+        res <- use pool2 $ Sessions.selectOne
+        res `shouldBe` Right 1
+        res2 <- use pool1 $ Sessions.countConnections appName2
+        res2 `shouldBe` Right 1
+        threadDelay 1_000_000 -- 1s
+        res3 <- use pool1 $ Sessions.countConnections appName2
+        res3 `shouldBe` Right 0
+
+  it "Passively times out old connections" \scopeParams -> do
+    -- 0.5s connection lifetime
+    Scripts.onAutotaggedPool 1 10 0.5 1_800 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+      res <- use pool $ Sessions.setSetting varName "hello world"
+      res `shouldBe` Right ()
+      res2 <- use pool $ Sessions.getSetting varName
+      res2 `shouldBe` Right (Just "hello world")
+      threadDelay 1_000_000 -- 1s
+      res3 <- use pool $ Sessions.getSetting varName
+      res3 `shouldBe` Right Nothing
diff --git a/src/integration-tests/Specs/BySubject/Config/IdlenessTimeoutSpec.hs b/src/integration-tests/Specs/BySubject/Config/IdlenessTimeoutSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/Config/IdlenessTimeoutSpec.hs
@@ -0,0 +1,29 @@
+module Specs.BySubject.Config.IdlenessTimeoutSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Times out old connections (maxIdletime)" \scopeParams -> do
+    -- 0.5s connection idle time
+    Scripts.onAutotaggedPool 1 10 1_800 0.5 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+      res <- use pool $ Sessions.setSetting varName "hello world"
+      res `shouldBe` Right ()
+      res2 <- use pool $ Sessions.getSetting varName
+      res2 `shouldBe` Right (Just "hello world")
+      -- busy sleep, to keep connection alive
+      forM_ [1 :: Int .. 10] $ \_ -> do
+        r <- use pool $ Sessions.selectOne
+        r `shouldBe` Right 1
+        threadDelay 100_000 -- 0.1s
+      res3 <- use pool $ Sessions.getSetting varName
+      res3 `shouldBe` Right (Just "hello world")
+      -- idle sleep, connection times out
+      threadDelay 1_000_000 -- 1s
+      res4 <- use pool $ Sessions.getSetting varName
+      res4 `shouldBe` Right Nothing
diff --git a/src/integration-tests/Specs/BySubject/Config/InitSessionSpec.hs b/src/integration-tests/Specs/BySubject/Config/InitSessionSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/Config/InitSessionSpec.hs
@@ -0,0 +1,43 @@
+module Specs.BySubject.Config.InitSessionSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Persists after exceptions thrown in session" \scopeParams -> do
+    Scripts.onAutotaggedPool 1 10 60 60 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+
+      res <- use pool do
+        Sessions.setSetting varName "1"
+        Sessions.getSetting varName
+      shouldBe res (Right (Just "1"))
+
+      try @SomeException do
+        use pool do
+          liftIO do
+            throwIO (userError "Intentional error for testing")
+
+      res <- use pool do
+        Sessions.getSetting varName
+      shouldBe res (Right (Just "1"))
+
+  it "Persists after bad query" \scopeParams -> do
+    Scripts.onAutotaggedPool 1 10 60 60 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+
+      res <- use pool do
+        Sessions.setSetting varName "1"
+        Sessions.getSetting varName
+      shouldBe res (Right (Just "1"))
+
+      use pool do
+        Sessions.badQuery
+
+      res <- use pool do
+        Sessions.getSetting varName
+      shouldBe res (Right (Just "1"))
diff --git a/src/integration-tests/Specs/BySubject/Helpers/Sessions/CountConnectionsSpec.hs b/src/integration-tests/Specs/BySubject/Helpers/Sessions/CountConnectionsSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/Helpers/Sessions/CountConnectionsSpec.hs
@@ -0,0 +1,14 @@
+module Specs.BySubject.Helpers.Sessions.CountConnectionsSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Counts active connections" \scopeParams -> do
+    Scripts.onAutotaggedPool 3 10 1_800 1_800 scopeParams \appName pool -> do
+      res <- use pool $ Sessions.countConnections appName
+      res `shouldBe` Right 1
diff --git a/src/integration-tests/Specs/BySubject/Helpers/Sessions/GetSettingSpec.hs b/src/integration-tests/Specs/BySubject/Helpers/Sessions/GetSettingSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/Helpers/Sessions/GetSettingSpec.hs
@@ -0,0 +1,36 @@
+module Specs.BySubject.Helpers.Sessions.GetSettingSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Getting and setting session variables works" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+      res <- use pool $ Sessions.getSetting varName
+      res `shouldBe` Right Nothing
+      res <- use pool $ do
+        Sessions.setSetting varName "hello world"
+        Sessions.getSetting varName
+      res `shouldBe` Right (Just "hello world")
+
+  it "Session variables stay set when a connection gets reused" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      varName <- Scripts.generateVarname
+      res <- use pool $ Sessions.setSetting varName "hello world"
+      res `shouldBe` Right ()
+      res2 <- use pool $ Sessions.getSetting varName
+      res2 `shouldBe` Right (Just "hello world")
+
+  it "Releasing the pool resets session variables" \scopeParams -> do
+    varName <- Scripts.generateVarname
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      res <- use pool $ Sessions.setSetting varName "hello world"
+      res `shouldBe` Right ()
+      release pool
+      res <- use pool $ Sessions.getSetting varName
+      res `shouldBe` Right Nothing
diff --git a/src/integration-tests/Specs/BySubject/ReleaseSpec.hs b/src/integration-tests/Specs/BySubject/ReleaseSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/ReleaseSpec.hs
@@ -0,0 +1,16 @@
+module Specs.BySubject.ReleaseSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "The pool remains usable after release" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      _ <- use pool $ Sessions.selectOne
+      release pool
+      res <- use pool $ Sessions.selectOne
+      shouldSatisfy res $ isRight
diff --git a/src/integration-tests/Specs/BySubject/SpecHook.hs b/src/integration-tests/Specs/BySubject/SpecHook.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/SpecHook.hs
@@ -0,0 +1,11 @@
+-- Docs: https://hspec.github.io/hspec-discover.html
+module Specs.BySubject.SpecHook where
+
+import Helpers.Hooks qualified as Hooks
+import Helpers.Scripts qualified as Scripts
+import Test.Hspec
+import Prelude
+
+hook :: SpecWith Scripts.ScopeParams -> Spec
+hook =
+  aroundAll Hooks.postgres17 . parallel
diff --git a/src/integration-tests/Specs/BySubject/UsageError/AcquisitionTimeoutSpec.hs b/src/integration-tests/Specs/BySubject/UsageError/AcquisitionTimeoutSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/UsageError/AcquisitionTimeoutSpec.hs
@@ -0,0 +1,33 @@
+module Specs.BySubject.UsageError.AcquisitionTimeoutSpec where
+
+import Control.Concurrent.Async (race)
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Gets produced on timeout" \scopeParams ->
+    -- 1ms timeout
+    Scripts.onAutotaggedPool 1 0.001 1_800 1_800 scopeParams \_ pool -> do
+      sleeping <- newEmptyMVar
+      t0 <- getCurrentTime
+      res <-
+        race
+          ( use pool
+              $ liftIO
+              $ do
+                putMVar sleeping ()
+                -- 1s
+                threadDelay 1_000_000
+          )
+          ( do
+              takeMVar sleeping
+              use pool $ Sessions.selectOne
+          )
+      t1 <- getCurrentTime
+      res `shouldBe` Right (Left AcquisitionTimeoutUsageError)
+      -- 0.5s
+      diffUTCTime t1 t0 `shouldSatisfy` (< 0.5)
diff --git a/src/integration-tests/Specs/BySubject/UsageError/SessionSpec.hs b/src/integration-tests/Specs/BySubject/UsageError/SessionSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/UsageError/SessionSpec.hs
@@ -0,0 +1,17 @@
+module Specs.BySubject.UsageError.SessionSpec where
+
+import Hasql.Pool
+import Hasql.Session qualified as Session
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Simulation of connection error works" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      res <- use pool $ Sessions.closeConn >> Sessions.selectOne
+      shouldSatisfy res $ \case
+        Left (SessionUsageError (Session.QueryError _ _ (Session.ClientError _))) -> True
+        _ -> False
diff --git a/src/integration-tests/Specs/BySubject/UseSpec.hs b/src/integration-tests/Specs/BySubject/UseSpec.hs
new file mode 100644
--- /dev/null
+++ b/src/integration-tests/Specs/BySubject/UseSpec.hs
@@ -0,0 +1,40 @@
+module Specs.BySubject.UseSpec where
+
+import Hasql.Pool
+import Helpers.Scripts qualified as Scripts
+import Helpers.Sessions qualified as Sessions
+import Test.Hspec
+import Prelude
+
+spec :: SpecWith Scripts.ScopeParams
+spec = do
+  it "Releases a spot in the pool when there is a query error" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      use pool Sessions.badQuery `shouldNotReturn` (Right ())
+      use pool Sessions.selectOne `shouldReturn` (Right 1)
+
+  it "Connection errors cause eviction of connection" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      _ <- use pool $ Sessions.closeConn >> Sessions.selectOne
+      _ <- use pool $ Sessions.closeConn >> Sessions.selectOne
+      _ <- use pool $ Sessions.closeConn >> Sessions.selectOne
+      res <- use pool $ Sessions.selectOne
+      shouldSatisfy res $ isRight
+
+  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
+      _ <- use pool $ Sessions.selectOne
+      _ <- use pool $ Sessions.selectOne
+      _ <- use pool $ Sessions.selectOne
+      res <- use pool $ Sessions.selectOne
+      shouldSatisfy res $ isRight
+
+  it "Connection gets returned to the pool after non-connection error" \scopeParams ->
+    Scripts.onAutotaggedPool 1 10 1_800 1_800 scopeParams \_ pool -> do
+      _ <- use pool $ Sessions.badQuery
+      _ <- use pool $ Sessions.badQuery
+      _ <- use pool $ Sessions.badQuery
+      _ <- use pool $ Sessions.badQuery
+      res <- use pool $ Sessions.selectOne
+      shouldSatisfy res $ isRight
diff --git a/src/test/Main.hs b/src/test/Main.hs
deleted file mode 100644
--- a/src/test/Main.hs
+++ /dev/null
@@ -1,228 +0,0 @@
-module Main where
-
-import Control.Concurrent.Async (race)
-import Data.Text qualified as Text
-import Hasql.Connection qualified as Connection
-import Hasql.Connection.Setting qualified as Connection.Setting
-import Hasql.Connection.Setting.Connection qualified as Connection.Setting.Connection
-import Hasql.Decoders qualified as Decoders
-import Hasql.Encoders qualified as Encoders
-import Hasql.Pool
-import Hasql.Pool.Config qualified as Config
-import Hasql.Session qualified as Session
-import Hasql.Statement qualified as Statement
-import System.Environment qualified
-import System.Random.Stateful qualified as Random
-import Test.Hspec
-import Prelude
-
-main :: IO ()
-main = do
-  connectionString <- getConnectionString
-  let withPool poolSize acqTimeout maxLifetime maxIdletime connectionString =
-        bracket
-          ( acquire
-              ( Config.settings
-                  [ Config.size poolSize,
-                    Config.acquisitionTimeout acqTimeout,
-                    Config.agingTimeout maxLifetime,
-                    Config.idlenessTimeout maxIdletime,
-                    Config.staticConnectionSettings
-                      [ Connection.Setting.connection
-                          (Connection.Setting.Connection.string connectionString)
-                      ]
-                  ]
-              )
-          )
-          release
-      withDefaultPool =
-        withPool 3 10 1_800 1_800 connectionString
-
-  hspec . describe "" $ do
-    it "Releases a spot in the pool when there is a query error" $ withDefaultPool $ \pool -> do
-      use pool badQuerySession `shouldNotReturn` (Right ())
-      use pool selectOneSession `shouldReturn` (Right 1)
-    it "Simulation of connection error works" $ withDefaultPool $ \pool -> do
-      res <- use pool $ closeConnSession >> selectOneSession
-      shouldSatisfy res $ \case
-        Left (SessionUsageError (Session.QueryError _ _ (Session.ClientError _))) -> True
-        _ -> False
-    it "Connection errors cause eviction of connection" $ withDefaultPool $ \pool -> do
-      _ <- use pool $ closeConnSession >> selectOneSession
-      _ <- use pool $ closeConnSession >> selectOneSession
-      _ <- use pool $ closeConnSession >> selectOneSession
-      res <- use pool $ selectOneSession
-      shouldSatisfy res $ isRight
-    it "Connection gets returned to the pool after normal use" $ withDefaultPool $ \pool -> do
-      _ <- use pool $ selectOneSession
-      _ <- use pool $ selectOneSession
-      _ <- use pool $ selectOneSession
-      _ <- use pool $ selectOneSession
-      res <- use pool $ selectOneSession
-      shouldSatisfy res $ isRight
-    it "Connection gets returned to the pool after non-connection error" $ withDefaultPool $ \pool -> do
-      _ <- use pool $ badQuerySession
-      _ <- use pool $ badQuerySession
-      _ <- use pool $ badQuerySession
-      _ <- use pool $ badQuerySession
-      res <- use pool $ selectOneSession
-      shouldSatisfy res $ isRight
-    it "The pool remains usable after release" $ withDefaultPool $ \pool -> do
-      _ <- use pool $ selectOneSession
-      release pool
-      res <- use pool $ selectOneSession
-      shouldSatisfy res $ isRight
-    it "Getting and setting session variables works" $ withDefaultPool $ \pool -> do
-      res <- use pool $ getSettingSession "testing.foo"
-      res `shouldBe` Right Nothing
-      res <- use pool $ do
-        setSettingSession "testing.foo" "hello world"
-        getSettingSession "testing.foo"
-      res `shouldBe` Right (Just "hello world")
-    it "Session variables stay set when a connection gets reused" $ withPool 1 10 1_800 1_800 connectionString $ \pool -> do
-      res <- use pool $ setSettingSession "testing.foo" "hello world"
-      res `shouldBe` Right ()
-      res2 <- use pool $ getSettingSession "testing.foo"
-      res2 `shouldBe` Right (Just "hello world")
-    it "Releasing the pool resets session variables" $ withPool 1 10 1_800 1_800 connectionString $ \pool -> do
-      res <- use pool $ setSettingSession "testing.foo" "hello world"
-      res `shouldBe` Right ()
-      release pool
-      res <- use pool $ getSettingSession "testing.foo"
-      res `shouldBe` Right Nothing
-    it "Times out connection acquisition"
-      $
-      -- 1ms timeout
-      withPool 1 0.001 1_800 1_800 connectionString
-      $ \pool -> do
-        sleeping <- newEmptyMVar
-        t0 <- getCurrentTime
-        res <-
-          race
-            ( use pool
-                $ liftIO
-                $ do
-                  putMVar sleeping ()
-                  threadDelay 1_000_000 -- 1s
-            )
-            ( do
-                takeMVar sleeping
-                use pool $ selectOneSession
-            )
-        t1 <- getCurrentTime
-        res `shouldBe` Right (Left AcquisitionTimeoutUsageError)
-        diffUTCTime t1 t0 `shouldSatisfy` (< 0.5) -- 0.5s
-    it "Passively times out old connections (maxLifetime)"
-      $
-      -- 0.5s connection lifetime
-      withPool 1 10 0.5 1_800 connectionString
-      $ \pool -> do
-        res <- use pool $ setSettingSession "testing.foo" "hello world"
-        res `shouldBe` Right ()
-        res2 <- use pool $ getSettingSession "testing.foo"
-        res2 `shouldBe` Right (Just "hello world")
-        threadDelay 1_000_000 -- 1s
-        res3 <- use pool $ getSettingSession "testing.foo"
-        res3 `shouldBe` Right Nothing
-    it "Counts active connections" $ do
-      (taggedConnectionSettings, appName) <- tagConnection connectionString
-      withPool 3 10 1_800 1_800 taggedConnectionSettings $ \pool -> do
-        res <- use pool $ countConnectionsSession appName
-        res `shouldBe` Right 1
-
-    it "Actively times out old connections (maxLifetime)" $ do
-      withDefaultPool $ \countPool -> do
-        (taggedConnectionSettings, appName) <- tagConnection connectionString
-        withPool 3 10 0.5 1_800 taggedConnectionSettings $ \limitedPool -> do
-          res <- use limitedPool $ selectOneSession
-          res `shouldBe` Right 1
-          res2 <- use countPool $ countConnectionsSession appName
-          res2 `shouldBe` Right 1
-          threadDelay 1_000_000 -- 1s
-          res3 <- use countPool $ countConnectionsSession appName
-          res3 `shouldBe` Right 0
-    it "Times out old connections (maxIdletime)" $ do
-      -- 0.5s connection idle time
-      withPool 1 10 1_800 0.5 connectionString $ \pool -> do
-        res <- use pool $ setSettingSession "testing.foo" "hello world"
-        res `shouldBe` Right ()
-        res2 <- use pool $ getSettingSession "testing.foo"
-        res2 `shouldBe` Right (Just "hello world")
-        -- busy sleep, to keep connection alive
-        forM_ [1 :: Int .. 10] $ \_ -> do
-          r <- use pool $ selectOneSession
-          r `shouldBe` Right 1
-          threadDelay 100_000 -- 0.1s
-        res3 <- use pool $ getSettingSession "testing.foo"
-        res3 `shouldBe` Right (Just "hello world")
-        -- idle sleep, connection times out
-        threadDelay 1_000_000 -- 1s
-        res4 <- use pool $ getSettingSession "testing.foo"
-        res4 `shouldBe` Right Nothing
-
-getConnectionString :: IO Text
-getConnectionString =
-  Text.unwords
-    . catMaybes
-    <$> sequence
-      [ setting "host" $ defaultEnv "POSTGRES_HOST" "localhost",
-        setting "port" $ defaultEnv "POSTGRES_PORT" "5432",
-        setting "user" $ defaultEnv "POSTGRES_USER" "postgres",
-        setting "password" $ defaultEnv "POSTGRES_PASSWORD" "postgres",
-        setting "dbname" $ defaultEnv "POSTGRES_DBNAME" "postgres"
-      ]
-  where
-    maybeEnv env = fmap Text.pack <$> System.Environment.lookupEnv env
-    defaultEnv env val = Just . fromMaybe val <$> maybeEnv env
-    setting label getEnv = do
-      val <- getEnv
-      return $ (\v -> label <> "=" <> v) <$> val
-
-tagConnection :: Text -> IO (Text, Text)
-tagConnection connectionString = do
-  tag <- Random.uniformWord32 Random.globalStdGen
-  let appName = "hasql-pool-test-" <> show tag
-  return (connectionString <> " application_name=" <> Text.pack appName, Text.pack appName)
-
-selectOneSession :: Session.Session Int64
-selectOneSession =
-  Session.statement () statement
-  where
-    statement = Statement.Statement "SELECT 1" Encoders.noParams decoder True
-    decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8))
-
-badQuerySession :: Session.Session ()
-badQuerySession =
-  Session.statement () statement
-  where
-    statement = Statement.Statement "zzz" Encoders.noParams Decoders.noResult True
-
-closeConnSession :: Session.Session ()
-closeConnSession = do
-  conn <- ask
-  liftIO $ Connection.release conn
-
-setSettingSession :: Text -> Text -> Session.Session ()
-setSettingSession name value = do
-  Session.statement (name, value) statement
-  where
-    statement = Statement.Statement "SELECT set_config($1, $2, false)" encoder Decoders.noResult True
-    encoder =
-      contramap fst (Encoders.param (Encoders.nonNullable Encoders.text))
-        <> contramap snd (Encoders.param (Encoders.nonNullable Encoders.text))
-
-getSettingSession :: Text -> Session.Session (Maybe Text)
-getSettingSession name = do
-  Session.statement name statement
-  where
-    statement = Statement.Statement "SELECT current_setting($1, true)" encoder decoder True
-    encoder = Encoders.param (Encoders.nonNullable Encoders.text)
-    decoder = Decoders.singleRow (Decoders.column (Decoders.nullable Decoders.text))
-
-countConnectionsSession :: Text -> Session.Session Int64
-countConnectionsSession appName = do
-  Session.statement appName statement
-  where
-    statement = Statement.Statement "SELECT count(*) FROM pg_stat_activity WHERE application_name = $1" encoder decoder True
-    encoder = Encoders.param (Encoders.nonNullable Encoders.text)
-    decoder = Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.int8))
