@@ -5,8 +5,67 @@ Copyright: (c) Eitan Chatav, 2017
5Maintainer: eitan@morphism.tech
5Maintainer: eitan@morphism.tech
6Stability: experimental
6Stability: experimental
8−`Squeal.PostgreSQL.Migration` defines a `Migration` type to safely
8+This module defines a `Migration` type to safely
9−change the schema of your database over time.
9+change the schema of your database over time. Let's see an example!
11+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
12+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
15+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
16+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
17+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
23+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
24+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
26+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
27+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
28+ , "email" ::: 'NoDef :=> 'Null 'PGtext
34+ makeUsers :: Step IO '[] '["users" ::: UsersTable]
36+ { name = "make users table"
37+ , up = void . define $
40+ (text & notNull) `As` #name :* Nil )
41+ ( primaryKey (Column #id :* Nil) `As` #pk_users :* Nil )
42+ , down = void . define $ dropTable #users
48+ makeEmails :: Step IO '["users" ::: UsersTable]
49+ '["users" ::: UsersTable, "emails" ::: EmailsTable]
51+ { name = "make emails table"
52+ , up = void . define $
55+ (int & notNull) `As` #user_id :*
56+ text `As` #email :* Nil )
57+ ( primaryKey (Column #id :* Nil) `As` #pk_emails :*
58+ foreignKey (Column #user_id :* Nil) #users (Column #id :* Nil)
59+ OnDeleteCascade OnUpdateCascade `As` #fk_user_id :* Nil )
60+ , down = void . define $ dropTable #emails
64+>>> let migration = makeUsers :>> makeEmails :>> Done
65+>>> withConnection "host=localhost port=5432 dbname=exampledb" (migrateUp migration)
66+NOTICE: relation "migration" already exists, skipping
67+>>> withConnection "host=localhost port=5432 dbname=exampledb" (migrateDown migration)
68+NOTICE: relation "migration" already exists, skipping
12{-# LANGUAGE
71{-# LANGUAGE
@@ -15,6 +74,7 @@ change the schema of your database over time.
15 , DataKinds
74 , DataKinds
17 , LambdaCase
76 , LambdaCase
18 , OverloadedLabels
78 , OverloadedLabels
19 , TypeApplications
79 , TypeApplications
20 , FlexibleContexts
80 , FlexibleContexts
@@ -36,9 +96,11 @@ module Squeal.PostgreSQL.Migration
38import Control.Category
98import Control.Category
99+-- import Control.Exception.Lifted
39import Control.Monad
100import Control.Monad
40import Control.Monad.Base
101import Control.Monad.Base
41import Control.Monad.Trans.Control
102import Control.Monad.Trans.Control
42import Generics.SOP (K(..))
104import Generics.SOP (K(..))
43import Data.Function ((&))
105import Data.Function ((&))
44import Data.Text (Text)
106import Data.Text (Text)
@@ -53,7 +115,7 @@ data Migration io schema0 schema1 where
53 :: Step io schema0 schema1
115 :: Step io schema0 schema1
54 -> Migration io schema1 schema2
116 -> Migration io schema1 schema2
55 -> Migration io schema0 schema2
117 -> Migration io schema0 schema2
56−infixl 7 :>>
118+infixr 7 :>>
57instance Category (Migration io) where
119instance Category (Migration io) where
58 id = Done
120 id = Done
59 (.) migration = \case
121 (.) migration = \case
@@ -69,7 +131,6 @@ data Step io schema0 schema1 = Step
69 , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
131 , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
73-- | Run a `Migration` by creating the `MigrationTable`
134-- | Run a `Migration` by creating the `MigrationTable`
74-- if it does not exist and then in a transaction, for each each `Step`
135-- if it does not exist and then in a transaction, for each each `Step`
75-- query to see if the `Step` is executed. If not, then
136-- query to see if the `Step` is executed. If not, then
@@ -83,9 +144,10 @@ migrateUp
84migrateUp migration =
145migrateUp migration =
85 define createMigration
146 define createMigration
86 & pqThen (begin defaultMode)
148 & pqThen (begin defaultMode)
87− & pqThen (upSteps migration)
150+ & pqThen (upSteps migration & rollbackOrCommit)
88− & pqThen (void commit)
@@ -97,18 +159,7 @@ migrateUp migration =
98 upSteps = \case
160 upSteps = \case
99 Done -> return ()
161 Done -> return ()
100− step :>> steps -> onExceptionRollback $
102− & pqThen (upSteps steps)
162+ step :>> steps -> upStep step & pqThen (upSteps steps)
106− => Step io schema0 schema1 -> PQ
107− ("migration" ::: MigrationTable ': schema0)
108− ("migration" ::: MigrationTable ': schema0)
111− ntuples <=< runQueryParams selectStep . Only . name
114 :: MonadBase IO io
165 :: MonadBase IO io
@@ -123,8 +174,20 @@ migrateUp migration =
123 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
174 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
125 pqEmbed (up step) -- safely switch schemas
176 pqEmbed (up step) -- safely switch schemas
126− & pqThen (manipulateParams insertStep (Only (name step)) & void))
177+ & pqThen (manipulateParams insertStep (Only (name step)))
127− -- insert up step execution record
179+ -- insert execution record
183+ => Step io schema0 schema1 -> PQ
184+ ("migration" ::: MigrationTable ': schema0)
185+ ("migration" ::: MigrationTable ': schema0)
187+ queryExecuted step = do
188+ result <- runQueryParams selectStep (Only (name step))
129-- | Rewind a `Migration` by creating the `MigrationTable`
192-- | Rewind a `Migration` by creating the `MigrationTable`
130-- if it does not exist and then in a transaction, for each each `Step`
193-- if it does not exist and then in a transaction, for each each `Step`
@@ -139,19 +202,21 @@ migrateDown
140migrateDown migration =
203migrateDown migration =
141 define createMigration
204 define createMigration
142 & pqThen (begin defaultMode)
206 & pqThen (begin defaultMode)
143− & pqThen (downSteps migration)
208+ & pqThen (downSteps migration & rollbackOrCommit)
144− & pqThen (void commit)
147− queryExecuted
211+ downSteps
148− :: MonadBase IO io
212+ :: MonadBaseControl IO io
149− => Step io schema0 schema1 -> PQ
213+ => Migration io schema0 schema1 -> PQ
150− ("migration" ::: MigrationTable ': schema1)
151 ("migration" ::: MigrationTable ': schema1)
214 ("migration" ::: MigrationTable ': schema1)
152− io RowNumber
215+ ("migration" ::: MigrationTable ': schema0)
153− queryExecuted =
216+ io ()
154− ntuples <=< runQueryParams selectStep . Only . name
217+ downSteps = \case
219+ step :>> steps -> downSteps steps & pqThen (downStep step)
157 :: MonadBase IO io
222 :: MonadBase IO io
@@ -166,20 +231,29 @@ migrateDown migration =
166 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
231 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
168 pqEmbed (down step) -- safely switch schemas
233 pqEmbed (down step) -- safely switch schemas
169− & pqThen (manipulateParams deleteStep (Only (name step)) & void))
234+ & pqThen (manipulateParams deleteStep (Only (name step)))
170− -- delete up step execution record
235+ -- delete execution record
172− downSteps
238+ queryExecuted
173− :: MonadBaseControl IO io
239+ :: MonadBase IO io
174− => Migration io schema0 schema1 -> PQ
240+ => Step io schema0 schema1 -> PQ
175 ("migration" ::: MigrationTable ': schema1)
241 ("migration" ::: MigrationTable ': schema1)
176− ("migration" ::: MigrationTable ': schema0)
242+ ("migration" ::: MigrationTable ': schema1)
177− io ()
243+ io RowNumber
178− downSteps = \case
244+ queryExecuted step = do
179− Done -> return ()
245+ result <- runQueryParams selectStep (Only (name step))
180− step :>> steps -> onExceptionRollback $
246+ okResult result
181− downSteps steps
247+ ntuples result
182− & pqThen (downStep step)
248+
249+okResult :: MonadBase IO io => K Result results -> PQ schema schema io ()
251+ status <- resultStatus result
252+ when (not (status `elem` [CommandOk, TuplesOk])) $ do
253+ errorMessageMaybe <- resultErrorMessage result
254+ case errorMessageMaybe of
255+ Nothing -> error "migrateDown: unknown error"
256+ Just msg -> error ("migrationDown: " <> show msg)
184-- | The `TableType` for a Squeal migration.
258-- | The `TableType` for a Squeal migration.
185type MigrationTable =
259type MigrationTable =