@@ -5,8 +5,67 @@ Copyright: (c) Eitan Chatav, 2017
55Maintainer: eitan@morphism.tech
66Stability: experimental
77
8−`Squeal.PostgreSQL.Migration` defines a `Migration` type to safely
9−change the schema of your database over time.
8+This module defines a `Migration` type to safely
9+change the schema of your database over time. Let's see an example!
10+
11+>>> :set -XDataKinds -XDeriveGeneric -XOverloadedLabels
12+>>> :set -XOverloadedStrings -XTypeApplications -XTypeOperators
13+>>> :{
14+type UsersTable =
15+ '[ "pk_users" ::: 'PrimaryKey '["id"] ] :=>
16+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
17+ , "name" ::: 'NoDef :=> 'NotNull 'PGtext
18+ ]
19+:}
20+
21+>>> :{
22+type EmailsTable =
23+ '[ "pk_emails" ::: 'PrimaryKey '["id"]
24+ , "fk_user_id" ::: 'ForeignKey '["user_id"] "users" '["id"]
25+ ] :=>
26+ '[ "id" ::: 'Def :=> 'NotNull 'PGint4
27+ , "user_id" ::: 'NoDef :=> 'NotNull 'PGint4
28+ , "email" ::: 'NoDef :=> 'Null 'PGtext
29+ ]
30+:}
31+
32+>>> :{
33+let
34+ makeUsers :: Step IO '[] '["users" ::: UsersTable]
35+ makeUsers = Step
36+ { name = "make users table"
37+ , up = void . define $
38+ createTable #users
39+ ( serial `As` #id :*
40+ (text & notNull) `As` #name :* Nil )
41+ ( primaryKey (Column #id :* Nil) `As` #pk_users :* Nil )
42+ , down = void . define $ dropTable #users
43+ }
44+:}
45+
46+>>> :{
47+let
48+ makeEmails :: Step IO '["users" ::: UsersTable]
49+ '["users" ::: UsersTable, "emails" ::: EmailsTable]
50+ makeEmails = Step
51+ { name = "make emails table"
52+ , up = void . define $
53+ createTable #emails
54+ ( serial `As` #id :*
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
61+ }
62+:}
63+
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
1069-}
1170
1271{-# LANGUAGE
@@ -15,6 +74,7 @@ change the schema of your database over time.
1574 , DataKinds
1675 , GADTs
1776 , LambdaCase
77+ , PolyKinds
1878 , OverloadedLabels
1979 , TypeApplications
2080 , FlexibleContexts
@@ -36,9 +96,11 @@ module Squeal.PostgreSQL.Migration
3696 ) where
3797
3898import Control.Category
99+-- import Control.Exception.Lifted
39100import Control.Monad
40101import Control.Monad.Base
41102import Control.Monad.Trans.Control
103+import Data.Monoid
42104import Generics.SOP (K(..))
43105import Data.Function ((&))
44106import Data.Text (Text)
@@ -53,7 +115,7 @@ data Migration io schema0 schema1 where
53115 :: Step io schema0 schema1
54116 -> Migration io schema1 schema2
55117 -> Migration io schema0 schema2
56−infixl 7 :>>
118+infixr 7 :>>
57119instance Category (Migration io) where
58120 id = Done
59121 (.) migration = \case
@@ -69,7 +131,6 @@ data Step io schema0 schema1 = Step
69131 , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
70132 }
71133
72−
73134-- | Run a `Migration` by creating the `MigrationTable`
74135-- if it does not exist and then in a transaction, for each each `Step`
75136-- query to see if the `Step` is executed. If not, then
@@ -83,9 +144,10 @@ migrateUp
83144 io ()
84145migrateUp migration =
85146 define createMigration
147+ & pqBind okResult
86148 & pqThen (begin defaultMode)
87− & pqThen (upSteps migration)
88− & pqThen (void commit)
149+ & pqBind okResult
150+ & pqThen (upSteps migration & rollbackOrCommit)
89151 where
90152
91153 upSteps
@@ -97,18 +159,7 @@ migrateUp migration =
97159 io ()
98160 upSteps = \case
99161 Done -> return ()
100− step :>> steps -> onExceptionRollback $
101− upStep step
102− & pqThen (upSteps steps)
103−
104− queryExecuted
105− :: MonadBase IO io
106− => Step io schema0 schema1 -> PQ
107− ("migration" ::: MigrationTable ': schema0)
108− ("migration" ::: MigrationTable ': schema0)
109− io RowNumber
110− queryExecuted =
111− ntuples <=< runQueryParams selectStep . Only . name
162+ step :>> steps -> upStep step & pqThen (upSteps steps)
112163
113164 upStep
114165 :: MonadBase IO io
@@ -123,8 +174,20 @@ migrateUp migration =
123174 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
124175 else
125176 pqEmbed (up step) -- safely switch schemas
126− & pqThen (manipulateParams insertStep (Only (name step)) & void))
127− -- insert up step execution record
177+ & pqThen (manipulateParams insertStep (Only (name step)))
178+ & pqBind okResult)
179+ -- insert execution record
180+
181+ queryExecuted
182+ :: MonadBase IO io
183+ => Step io schema0 schema1 -> PQ
184+ ("migration" ::: MigrationTable ': schema0)
185+ ("migration" ::: MigrationTable ': schema0)
186+ io RowNumber
187+ queryExecuted step = do
188+ result <- runQueryParams selectStep (Only (name step))
189+ okResult result
190+ ntuples result
128191
129192-- | Rewind a `Migration` by creating the `MigrationTable`
130193-- if it does not exist and then in a transaction, for each each `Step`
@@ -139,19 +202,21 @@ migrateDown
139202 io ()
140203migrateDown migration =
141204 define createMigration
205+ & pqBind okResult
142206 & pqThen (begin defaultMode)
143− & pqThen (downSteps migration)
144− & pqThen (void commit)
207+ & pqBind okResult
208+ & pqThen (downSteps migration & rollbackOrCommit)
145209 where
146210
147− queryExecuted
148− :: MonadBase IO io
149− => Step io schema0 schema1 -> PQ
150− ("migration" ::: MigrationTable ': schema1)
211+ downSteps
212+ :: MonadBaseControl IO io
213+ => Migration io schema0 schema1 -> PQ
151214 ("migration" ::: MigrationTable ': schema1)
152− io RowNumber
153− queryExecuted =
154− ntuples <=< runQueryParams selectStep . Only . name
215+ ("migration" ::: MigrationTable ': schema0)
216+ io ()
217+ downSteps = \case
218+ Done -> return ()
219+ step :>> steps -> downSteps steps & pqThen (downStep step)
155220
156221 downStep
157222 :: MonadBase IO io
@@ -166,20 +231,29 @@ migrateDown migration =
166231 then PQ (\ _ -> return (K ())) -- unsafely switch schemas
167232 else
168233 pqEmbed (down step) -- safely switch schemas
169− & pqThen (manipulateParams deleteStep (Only (name step)) & void))
170− -- delete up step execution record
234+ & pqThen (manipulateParams deleteStep (Only (name step)))
235+ -- delete execution record
236+ & pqBind okResult)
171237
172− downSteps
173− :: MonadBaseControl IO io
174− => Migration io schema0 schema1 -> PQ
238+ queryExecuted
239+ :: MonadBase IO io
240+ => Step io schema0 schema1 -> PQ
175241 ("migration" ::: MigrationTable ': schema1)
176− ("migration" ::: MigrationTable ': schema0)
177− io ()
178− downSteps = \case
179− Done -> return ()
180− step :>> steps -> onExceptionRollback $
181− downSteps steps
182− & pqThen (downStep step)
242+ ("migration" ::: MigrationTable ': schema1)
243+ io RowNumber
244+ queryExecuted step = do
245+ result <- runQueryParams selectStep (Only (name step))
246+ okResult result
247+ ntuples result
248+
249+okResult :: MonadBase IO io => K Result results -> PQ schema schema io ()
250+okResult result = do
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)
183257
184258-- | The `TableType` for a Squeal migration.
185259type MigrationTable =