packages feed

pg-migrate-test-support 1.0.0.0 → 1.1.0.0

raw patch · 4 files changed

+57/−20 lines, 4 filesdep ~pg-migratePVP ok

version bump matches the API change (PVP)

Dependency ranges changed: pg-migrate

API changes (from Hackage documentation)

- Database.PostgreSQL.Migrate.Test: MigratedDatabaseCallbackCleanupFailed :: !SomeException -> MigratedDatabaseError

Files

CHANGELOG.md view
@@ -1,5 +1,24 @@ # Changelog +## 1.1.0.0 — 2026-07-13++Major release: this set removes a public error constructor and changes callback exception+behavior.++### Breaking changes++- Removed `MigratedDatabaseCallbackCleanupFailed`; a successful callback value now wins+  when only its connection release fails.+- Asynchronous callback exceptions now propagate after cleanup instead of being returned as+  `MigratedDatabaseCallbackFailed`.++### Fixes and behavior changes++- Acquire the callback connection inside the same masked bracket that runs the callback and+  releases the connection, closing the interruption leak window.+- Preserve both exceptions in `MigratedDatabaseCallbackAndCleanupFailed` when a synchronous+  callback failure and connection-release failure occur together.+ ## 1.0.0.0 — 2026-07-10  - Initial stable release of the `ephemeral-pg` helper with fresh Hasql callback connections
pg-migrate-test-support.cabal view
@@ -1,6 +1,6 @@ cabal-version:   3.8 name:            pg-migrate-test-support-version:         1.0.0.0+version:         1.1.0.0 synopsis:        Ephemeral PostgreSQL helpers for pg-migrate tests category:        Database maintainer:      nadeem@gmail.com@@ -31,7 +31,7 @@     , base          >=4.20 && <4.22     , ephemeral-pg  >=0.2  && <0.3     , hasql         >=1.10 && <1.11-    , pg-migrate    >=1.0  && <1.1+    , pg-migrate    >=1.1  && <1.2  test-suite pg-migrate-test-support-test   import:         common
src/Database/PostgreSQL/Migrate/Test.hs view
@@ -1,7 +1,7 @@ -- | Test-only ephemeral PostgreSQL lifecycle. A validated plan is applied before a fresh -- Hasql connection is bracketed around the caller's assertion callback. module Database.PostgreSQL.Migrate.Test-  ( -- | Structured startup, migration, callback, and cleanup failures.+  ( -- | Structured startup, migration, callback, and callback-plus-cleanup failures.     MigratedDatabaseError (..),     -- | Start a database, apply a plan with default runner options, and run a callback.     withMigratedDatabase,@@ -17,15 +17,17 @@ import Database.PostgreSQL.Migrate import EphemeralPg qualified import Hasql.Connection qualified as Connection+import Hasql.Connection.Settings qualified as Settings import Hasql.Errors qualified as Errors --- | Structured failures from the ephemeral database, migration, callback, or cleanup stages.+-- | Structured failures from the ephemeral database, migration, callback, or a callback+-- failure accompanied by a connection-release failure. A release failure after a+-- successful callback does not replace its value. data MigratedDatabaseError   = MigratedDatabaseStartupFailed !EphemeralPg.StartError   | MigratedDatabaseMigrationFailed !MigrationError   | MigratedDatabaseCallbackAcquisitionFailed !Errors.ConnectionError   | MigratedDatabaseCallbackFailed !SomeException-  | MigratedDatabaseCallbackCleanupFailed !SomeException   | MigratedDatabaseCallbackAndCleanupFailed !SomeException !SomeException   deriving stock (Show) @@ -45,6 +47,7 @@ withMigratedDatabaseOptions = withMigratedDatabaseConfig EphemeralPg.defaultConfig  -- | Fully configurable variant accepting both ephemeral database and migration options.+-- Asynchronous callback exceptions are rethrown after releasing the callback connection. withMigratedDatabaseConfig ::   EphemeralPg.Config ->   RunOptions ->@@ -58,26 +61,30 @@       migrated <- runMigrationPlan options settings plan       case migrated of         Left migrationError -> pure (Left (MigratedDatabaseMigrationFailed migrationError))-        Right _ -> do-          acquired <- Connection.acquire settings-          case acquired of-            Left connectionError -> pure (Left (MigratedDatabaseCallbackAcquisitionFailed connectionError))-            Right connection -> runCallback connection callback+        Right _ -> runCallback settings callback   pure $ case started of     Left startError -> Left (MigratedDatabaseStartupFailed startError)     Right result -> result  runCallback ::-  Connection.Connection ->+  Settings.Settings ->   (Connection.Connection -> IO value) ->   IO (Either MigratedDatabaseError value)-runCallback connection callback =+runCallback settings callback =   Exception.mask $ \restore -> do-    callbackResult <- Exception.try (restore (callback connection))-    cleanupResult <- Exception.try (Connection.release connection)-    pure $ case (callbackResult, cleanupResult) of-      (Right value, Right ()) -> Right value-      (Left callbackError, Right ()) -> Left (MigratedDatabaseCallbackFailed callbackError)-      (Right _, Left cleanupError) -> Left (MigratedDatabaseCallbackCleanupFailed cleanupError)-      (Left callbackError, Left cleanupError) ->-        Left (MigratedDatabaseCallbackAndCleanupFailed callbackError cleanupError)+    acquired <- Connection.acquire settings+    case acquired of+      Left connectionError -> pure (Left (MigratedDatabaseCallbackAcquisitionFailed connectionError))+      Right connection -> do+        callbackResult <- Exception.try @SomeException (restore (callback connection))+        cleanupResult <- Exception.try @SomeException (Connection.release connection)+        case callbackResult of+          Left callbackError+            | Just _ <- Exception.fromException @Exception.SomeAsyncException callbackError ->+                Exception.throwIO callbackError+          _ ->+            pure $ case (callbackResult, cleanupResult) of+              (Right value, _) -> Right value+              (Left callbackError, Right ()) -> Left (MigratedDatabaseCallbackFailed callbackError)+              (Left callbackError, Left cleanupError) ->+                Left (MigratedDatabaseCallbackAndCleanupFailed callbackError cleanupError)
test/Main.hs view
@@ -24,6 +24,7 @@         [ testCase "plan is migrated before a fresh callback connection" testMigratedDatabase,           testCase "migration failures are structurally distinct" testMigrationFailure,           testCase "callback failures are structurally distinct" testCallbackFailure,+          testCase "asynchronous callback exceptions propagate" testAsyncCallbackException,           testCase "startup failures are structurally distinct" testStartupFailure         ]     )@@ -51,6 +52,16 @@   case result of     Left MigratedDatabaseCallbackFailed {} -> pure ()     other -> assertFailure ("expected callback failure, received: " <> show other)++testAsyncCallbackException :: Assertion+testAsyncCallbackException = do+  result <-+    Exception.try @Exception.AsyncException+      (withMigratedDatabase fixturePlan (const (Exception.throwIO Exception.UserInterrupt) :: Connection.Connection -> IO ()))+  case result of+    Left Exception.UserInterrupt -> pure ()+    Left exception -> assertFailure ("expected UserInterrupt, received: " <> show exception)+    Right callbackResult -> assertFailure ("expected propagated async exception, received: " <> show callbackResult)  testStartupFailure :: Assertion testStartupFailure = do