postgresql-migration-persistent (empty) → 1.0.0
raw patch · 5 files changed
+303/−0 lines, 5 filesdep +basedep +mtldep +persistent
Dependencies added: base, mtl, persistent, persistent-postgresql, postgresql-migration, postgresql-simple, resource-pool, text
Files
- Changelog.md +10/−0
- LICENSE +21/−0
- Readme.md +83/−0
- postgresql-migration-persistent.cabal +79/−0
- src/PostgreSQL/Migration/Persistent.hs +110/−0
+ Changelog.md view
@@ -0,0 +1,10 @@+# Change log for postgresql-migration-persistent project++## Version 1.0.0+import the code+++## Version 0.0.0 ++import [template](https://github.com/jappeace/template).+
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2025 Jappie Klooster++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Readme.md view
@@ -0,0 +1,83 @@++> Don't you *ever* trust computers.+> +> \- first rule of programist bible.+++This library combines [`postgresql-migration`](https://hackage.haskell.org/package/postgresql-migration) and [`persistent`](https://hackage.haskell.org/package/persistent),+for the common use case of:+1. Run my manually defined migrations.+2. Check if the schema defined in persistent aligns with the database.+3. If not, rollback and error with the migration plan persistent wants to do.++## Usage++It could for example look something like this with katip logging:+```haskell+import PostgreSQL.Migration.Persistent+import Database.Schema.User()+import Database.Schema.Company()++migrateAll :: Migration+migrateAll = migrateModels $(discoverEntities)++main :: IO ()+main = do+ katipConfig <- mkKatipConfig "server" environment+ let migDir = "migrations/up"+ let migrationOptions = defaultOptions migDir $ \case+ Left errmsg -> runContext katipConfig $ $logTM AlertS $ logStr errmsg+ Right infoMsg -> runContext katipConfig $ $logTM InfoS $ logStr infoMsg++ result <- runMigrations migrationOptions migrateAll rawPool+ runContext katipConfig $ case result of+ MigrationConsistent -> $logTM InfoS "migration consistent"+ MigrationRollbackDueTo rollback -> do+ $logTM ErrorS $ logStr $ errorMessage rollback+ error "invalid migrations"+ MigrationLibraryError err' -> do+ $logTM ErrorS $ logStr err'+ error "migration library error"+ MigrationNotBackedByPg ->+ error "app expects pg backing for migrations to work"+ + runMyApp+```++By default the migrations are applied in a large transaction,+but you can modify this behavior by overriding options record,+for example:++```haskell+import Database.PostgreSQL.Simple.Migration qualified as Migration++main = do+ ...+ let migrationOptions = defaultOptions migDir $ \case+ Left errmsg -> runContext katipConfig $ $logTM AlertS $ logStr errmsg+ Right infoMsg -> runContext katipConfig $ $logTM InfoS $ logStr infoMsg+ let overridenOptions = migrationOptions { pmoMigrationOptions = (pmoMigrationOptions migrationOptions ) {Migration.optTransactionControl = Migration.TransactionPerStep }}+```++pretty much all options are exposed from the underlying postgresql-migration+library.++### Tools+Enter the nix shell.+```+nix develop+```+You can checkout the makefile to see what's available:+```+cat makefile+```++### Running+```+make run+```++### Fast filewatch which runs tests+```+make ghcid+```
+ postgresql-migration-persistent.cabal view
@@ -0,0 +1,79 @@+cabal-version: 3.0++name: postgresql-migration-persistent+version: 1.0.0+homepage: https://github.com/jappeace/postgresql-migration-persistent#readme+bug-reports: https://github.com/jappeace/postgresql-migration-persistent/issues+author: Jappie Klooster, Jean-Paul Calderone +maintainer: hi@jappie.me+synopsis: A PostgreSQL persistent schema migration utility+description: Wraps postgresql migration and persistent to make sure the persistent schema+ aligns with what's in the database.+ If not, it returns a list of suggested manual migrations+ to be put in postgresql-migration.+copyright: 2025 Jappie Klooster+license: MIT+license-file: LICENSE+build-type: Simple+category: Database+extra-source-files:+ Readme.md+ LICENSE+extra-doc-files:+ Changelog.md++source-repository head+ type: git+ location: https://github.com/jappeace/postgresql-migration-persistent++common common-options+ default-extensions: + EmptyCase+ FlexibleContexts+ FlexibleInstances+ InstanceSigs+ MultiParamTypeClasses+ LambdaCase+ MultiWayIf+ NamedFieldPuns+ TupleSections+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveLift+ DeriveTraversable+ DerivingStrategies+ GeneralizedNewtypeDeriving+ StandaloneDeriving+ OverloadedStrings+ TypeApplications+ NumericUnderscores+ ImportQualifiedPost++ ghc-options:+ -Wall -Wincomplete-uni-patterns+ -Wincomplete-record-updates -Widentities -Wredundant-constraints+ -Wcpp-undef -fwarn-tabs -Wpartial-fields+ -fdefer-diagnostics -Wunused-packages+ -fenable-th-splice-warnings+ -fno-omit-yields+ -threaded ++ build-depends:+ base >=4.9.1.0 && <5+ , postgresql-migration < 0.3+ , persistent-postgresql < 3+ , persistent < 3+ , postgresql-simple < 1+ , text < 3+ , resource-pool < 0.5+ , mtl < 3++ default-language: Haskell2010++library+ import: common-options+ exposed-modules:+ PostgreSQL.Migration.Persistent+ hs-source-dirs:+ src
+ src/PostgreSQL/Migration/Persistent.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE DataKinds #-}++-- | uses Postgres migration to run all found+-- migrations in a transaction.+-- then use persistent to check if the persistent+-- model aligns with what's in the database.+module PostgreSQL.Migration.Persistent+ ( runMigrations,+ PMMigrationResult (..),++ -- * options+ defaultOptions,+ PersistentMigrationOptions (..),+ )+where++import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ReaderT (..), asks)+import Data.Pool (Pool)+import Data.Text (Text)+import Database.Persist.Postgresql (getSimpleConn, runSqlPool)+import Database.Persist.Sql (Migration, SqlBackend, showMigration)+import Database.PostgreSQL.Simple (Connection, rollback)+import Database.PostgreSQL.Simple.Migration qualified as Migration+import Database.PostgreSQL.Simple.Util qualified as Migration+import Prelude++-- | recommended default options for production settnigs+defaultOptions ::+ -- | migrations folder, for exmaple "migrations/up"+ FilePath ->+ -- | the logging options, for example+ --+ -- @+ -- (\case+ -- Left errmsg -> runInIO $ $logTM AlertS $ logStr errmsg+ -- Right infoMsg -> runInIO $ $logTM InfoS $ logStr infoMsg)+ -- @+ (Either Text Text -> IO ()) ->+ PersistentMigrationOptions+defaultOptions filepath logMsgs =+ PersistentMigrationOptions+ { pmoMigrationOptions =+ Migration.defaultOptions+ { Migration.optVerbose = Migration.Verbose,+ Migration.optLogWriter = logMsgs,+ -- NB we do the transaction around the entire thing+ Migration.optTransactionControl = Migration.NoNewTransaction+ },+ pmoMigrationSource = Migration.MigrationDirectory filepath,+ pmoSchemaMigrationTableName = "schema_migrations"+ }++-- | The result of the postgresql-migration operation.+data PMMigrationResult+ = MigrationConsistent+ | -- | rollback due to persistent inconsistencies+ MigrationRollbackDueTo [Text]+ | -- | some error from the postgres migration libraries+ MigrationLibraryError String+ | -- | caused by 'getSimpleConn' returning 'Nothing'+ MigrationNotBackedByPg+ deriving stock (Show, Eq)++data PersistentMigrationOptions = PersistentMigrationOptions+ { pmoMigrationOptions :: Migration.MigrationOptions,+ pmoMigrationSource :: Migration.MigrationCommand,+ pmoSchemaMigrationTableName :: String+ }++-- | Run the given migrations in a single transaction. If the migration fails+-- somehow the transaction is rolled back.+runMigrations ::+ -- | eg 'defaultOptions'+ PersistentMigrationOptions ->+ -- | the Automatic migration. eg 'migrateAll', or migrateModels $(discoverEntities). note this is+ Migration ->+ -- | sql pool, created with for example 'withPostgresqlPool'.+ Pool SqlBackend ->+ IO PMMigrationResult+runMigrations config migrateAll pool =+ flip runSqlPool pool $ do+ asks getSimpleConn >>= \case+ Nothing -> pure $ MigrationNotBackedByPg+ Just conn -> do+ result <- runMigrationCommands config conn+ case result of+ Migration.MigrationError err ->+ pure $ MigrationLibraryError err+ Migration.MigrationSuccess ->+ assertPersistentConsistency migrateAll >>= \case+ MigrationConsistent -> pure MigrationConsistent+ failure -> liftIO $ failure <$ rollback conn++runMigrationCommands :: PersistentMigrationOptions -> Connection -> ReaderT SqlBackend IO (Migration.MigrationResult String)+runMigrationCommands options conn = do+ initialized <- liftIO $ Migration.existsTable conn $ pmoSchemaMigrationTableName options+ let migrations =+ if initialized+ then [pmoMigrationSource options]+ else [Migration.MigrationInitialization, pmoMigrationSource options]+ liftIO $ Migration.runMigrations conn (pmoMigrationOptions options) migrations++assertPersistentConsistency :: Migration -> ReaderT SqlBackend IO PMMigrationResult+assertPersistentConsistency migrateAll = do+ persistentAutoMig <- showMigration migrateAll+ if null persistentAutoMig+ then pure MigrationConsistent+ else do+ pure $ MigrationRollbackDueTo persistentAutoMig