@@ -11,11 +11,13 @@
13module Squeal.PostgreSQL.Migration
13module Squeal.PostgreSQL.Migration
14− ( Migration (..)
14+ ( -- * Migration
15− , Step (..)
15+ Migration (..)
17 , migrateUp
16 , migrateUp
18 , migrateDown
17 , migrateDown
19 , MigrationTable
21 , MigrationTable
20 , createMigration
22 , createMigration
21 , insertStep
23 , insertStep
@@ -29,19 +31,22 @@ import Control.Monad.Base
29import Control.Monad.Trans.Control
31import Control.Monad.Trans.Control
30import Generics.SOP (K(..))
32import Generics.SOP (K(..))
31import Data.Function ((&))
33import Data.Function ((&))
34+import Data.Text (Text)
32import Prelude hiding (id, (.))
35import Prelude hiding (id, (.))
34import Squeal.PostgreSQL
37import Squeal.PostgreSQL
35import Squeal.PostgreSQL.Transaction
38import Squeal.PostgreSQL.Transaction
40+-- | A `Step` of a `Migration`, should contain an inverse pair of
41+-- `up` and `down` instructions.
37data Step io schema0 schema1 = Step
42data Step io schema0 schema1 = Step
43+ { name :: Text -- ^ The `name` of a `Step`.
44+ -- Each `name` in a `Migration` should be unique.
38− { up :: PQ schema0 schema1 io ()
45+ , up :: PQ schema0 schema1 io () -- ^ The `up` instruction of a `Step`.
39− , down :: PQ schema1 schema0 io ()
46+ , down :: PQ schema1 schema0 io () -- ^ The `down` instruction of a `Step`.
42−skip :: Monad io => Step io schema schema -> Step io schema schema
49+-- | A `Migration` is a chain of `Step`s
43−skip _ = Step (return ()) (return ())
45data Migration io schema0 schema1 where
50data Migration io schema0 schema1 where
46 Done :: Migration io schema schema
51 Done :: Migration io schema schema
@@ -55,28 +60,126 @@ instance Category (Migration io) where
55 Done -> migration
60 Done -> migration
56 step :>> steps -> step :>> (steps >>> migration)
61 step :>> steps -> step :>> (steps >>> migration)
64+-- | Run a `Migration` by creating the `MigrationTable`
65+-- if it does not exist and then for each each `Step`
66+-- query to see if the `Step` is executed. If not, then
67+-- execute the `Step` and insert its row in `MigrationTable`.
59 :: MonadBaseControl IO io
69 :: MonadBaseControl IO io
60− => Migration io schema0 schema1 -> PQ
70+ => Migration io schema0 schema1 -- ^ migration to run
61 ("migration" ::: MigrationTable ': schema0)
72 ("migration" ::: MigrationTable ': schema0)
62 ("migration" ::: MigrationTable ': schema1)
73 ("migration" ::: MigrationTable ': schema1)
64−migrateUp migration = start & pqThen (upSteps migration & pqBind end)
75+migrateUp migration =
65−
76+ define createMigration
77+ & pqThen (begin (TransactionMode RepeatableRead ReadWrite))
78+ & pqThen (upSteps migration)
79+ & pqThen (void commit)
83+ :: MonadBaseControl IO io
84+ => Migration io schema0 schema1
86+ ("migration" ::: MigrationTable ': schema0)
87+ ("migration" ::: MigrationTable ': schema1)
91+ step :>> steps -> onExceptionRollback $
93+ & pqThen (upSteps steps)
97+ => Step io schema0 schema1 -> PQ
98+ ("migration" ::: MigrationTable ': schema0)
99+ ("migration" ::: MigrationTable ': schema0)
102+ ntuples <=< runQueryParams selectStep . Only . name
106+ => Step io schema0 schema1 -> PQ
107+ ("migration" ::: MigrationTable ': schema0)
108+ ("migration" ::: MigrationTable ': schema1)
112+ & pqBind (\ (RowNumber executed) ->
113+ if executed == 1 -- up step has already been executed
114+ then PQ (\ _ -> return (K ())) -- unsafely switch schemas
116+ pqEmbed (up step) -- safely switch schemas
117+ & pqThen (manipulateParams insertStep (Only (name step)) & void))
118+ -- insert up step execution record
120+-- | Rewind a `Migration` by creating the `MigrationTable`
121+-- if it does not exist and then for each each `Step`
122+-- query to see if the `Step` is executed. If it is, then
123+-- rewind the `Step` and delete its row in `MigrationTable`.
66migrateDown
124migrateDown
67 :: MonadBaseControl IO io
125 :: MonadBaseControl IO io
68− => Migration io schema0 schema1 -> PQ
126+ => Migration io schema0 schema1 -- ^ migration to rewind
69− ("migration" ::: MigrationTable ': schema1)
128+ ("migration" ::: MigrationTable ': schema1)
70− ("migration" ::: MigrationTable ': schema0)
129+ ("migration" ::: MigrationTable ': schema0)
131+migrateDown migration =
132+ define createMigration
133+ & pqThen (begin (TransactionMode RepeatableRead ReadWrite) & void)
72−migrateDown migration = start & pqThen (downSteps migration & pqBind end)
134+ & pqThen (downSteps migration)
73−
135+ & pqThen (void commit)
140+ => Step io schema0 schema1 -> PQ
141+ ("migration" ::: MigrationTable ': schema1)
142+ ("migration" ::: MigrationTable ': schema1)
145+ ntuples <=< runQueryParams selectStep . Only . name
149+ => Step io schema0 schema1 -> PQ
150+ ("migration" ::: MigrationTable ': schema1)
151+ ("migration" ::: MigrationTable ': schema0)
155+ & pqBind (\ (RowNumber executed) ->
156+ if executed == 0 -- up step has not been executed
157+ then PQ (\ _ -> return (K ())) -- unsafely switch schemas
159+ pqEmbed (down step) -- safely switch schemas
160+ & pqThen (manipulateParams deleteStep (Only (name step)) & void))
161+ -- delete up step execution record
164+ :: MonadBaseControl IO io
165+ => Migration io schema0 schema1 -> PQ
166+ ("migration" ::: MigrationTable ': schema1)
167+ ("migration" ::: MigrationTable ': schema0)
171+ step :>> steps -> onExceptionRollback $
173+ & pqThen (downStep step)
175+-- | The `TableType` for a Squeal migration.
74type MigrationTable =
176type MigrationTable =
75 '[ "unique_name" ::: 'Unique '["name"]] :=>
177 '[ "unique_name" ::: 'Unique '["name"]] :=>
76 '[ "name" ::: 'NoDef :=> 'NotNull 'PGtext
178 '[ "name" ::: 'NoDef :=> 'NotNull 'PGtext
77 , "executed_at" ::: 'Def :=> 'NotNull 'PGtimestamptz
179 , "executed_at" ::: 'Def :=> 'NotNull 'PGtimestamptz
182+-- | Creates a `MigrationTable` if it does not already exist.
80createMigration
183createMigration
81 :: Has "migration" schema MigrationTable
184 :: Has "migration" schema MigrationTable
82 => Definition schema schema
185 => Definition schema schema
@@ -87,6 +190,7 @@ createMigration =
87 `As` #executed_at :* Nil )
190 `As` #executed_at :* Nil )
88 ( unique (Column #name :* Nil) `As` #unique_name :* Nil )
191 ( unique (Column #name :* Nil) `As` #unique_name :* Nil )
193+-- | Inserts a `Step` into the `MigrationTable`
90insertStep
194insertStep
91 :: Has "migration" schema MigrationTable
195 :: Has "migration" schema MigrationTable
92 => Manipulation schema '[ 'NotNull 'PGtext] '[]
196 => Manipulation schema '[ 'NotNull 'PGtext] '[]
@@ -94,11 +198,14 @@ insertStep = insertRow_ #migration
94 ( Set (param @1) `As` #name :*
198 ( Set (param @1) `As` #name :*
95 Default `As` #executed_at :* Nil )
199 Default `As` #executed_at :* Nil )
201+-- | Deletes a `Step` from the `MigrationTable`
97deleteStep
202deleteStep
98 :: Has "migration" schema MigrationTable
203 :: Has "migration" schema MigrationTable
99 => Manipulation schema '[ 'NotNull 'PGtext ] '[]
204 => Manipulation schema '[ 'NotNull 'PGtext ] '[]
100deleteStep = deleteFrom_ #migration (#name .== param @1)
205deleteStep = deleteFrom_ #migration (#name .== param @1)
207+-- | Selects a `Step` from the `MigrationTable`, returning
208+-- the time at which it was executed.
102selectStep
209selectStep
103 :: Has "migration" schema MigrationTable
210 :: Has "migration" schema MigrationTable
104 => Query schema '[ 'NotNull 'PGtext ]
211 => Query schema '[ 'NotNull 'PGtext ]
@@ -107,43 +214,3 @@ selectStep = select
107 (#executed_at `As` #executed_at :* Nil)
214 (#executed_at `As` #executed_at :* Nil)
108 ( from (table (#migration `As` #m))
215 ( from (table (#migration `As` #m))
109 & where_ (#name .== param @1))
216 & where_ (#name .== param @1))
112− :: MonadBase IO io => PQ
113− ("migration" ::: MigrationTable ': schema)
114− ("migration" ::: MigrationTable ': schema)
116−start = define createMigration
117− & pqThen (begin (TransactionMode RepeatableRead ReadWrite) & void)
120− :: MonadBaseControl IO io
121− => Migration io schema0 schema1
123− ("migration" ::: MigrationTable ': schema0)
124− ("migration" ::: MigrationTable ': schema1)
128− step :>> steps -> onExceptionRollback $
129− pqEmbed (up step) & pqThen (upSteps steps)
132− :: MonadBaseControl IO io
133− => Migration io schema0 schema1 -> PQ
134− ("migration" ::: MigrationTable ': schema1)
135− ("migration" ::: MigrationTable ': schema0)
139− step :>> steps -> onExceptionRollback $
140− downSteps steps & pqThen (pqEmbed (down step))
142−end :: MonadBase IO io => result -> PQ
143− ("migration" ::: MigrationTable ': schema)
144− ("migration" ::: MigrationTable ': schema)
146−end result = commit >> return result
148−avoid :: Monad m => PQ schema0 schema1 m ()
149−avoid = PQ $ \ _ -> return (K ())