21
21
module Database.PostgreSQL.Simple.Migration
22
22
(
23
23
-- * Migration actions
24
- runMigration
24
+ runMigration'
25
+ , runMigration
25
26
, runMigrations
27
+ , runMigrations'
26
28
, sequenceMigrations
27
29
28
30
-- * Migration types
29
31
, MigrationContext (.. )
32
+ , MigrationContext' (.. )
30
33
, MigrationCommand (.. )
31
34
, MigrationResult (.. )
32
35
, ScriptName
@@ -45,6 +48,7 @@ import Control.Applicative ((<$>), (<*>))
45
48
import Control.Monad (void , when )
46
49
import qualified Crypto.Hash.MD5 as MD5 (hash )
47
50
import qualified Data.ByteString as BS (ByteString , readFile )
51
+ import qualified Data.ByteString.Char8 as BS8 (unpack )
48
52
import qualified Data.ByteString.Base64 as B64 (encode )
49
53
import Data.Foldable (Foldable )
50
54
import Data.List (isPrefixOf , sort )
@@ -71,19 +75,23 @@ import System.Directory (getDirectoryContents)
71
75
--
72
76
-- It is recommended to wrap 'runMigration' inside a database transaction.
73
77
runMigration :: MigrationContext -> IO (MigrationResult String )
74
- runMigration (MigrationContext cmd verbose con) = case cmd of
78
+ runMigration (MigrationContext cmd verbose con) =
79
+ runMigration' (MigrationContext' cmd verbose con " schema_migrations" )
80
+
81
+ runMigration' :: MigrationContext' -> IO (MigrationResult String )
82
+ runMigration' (MigrationContext' cmd verbose con tableName) = case cmd of
75
83
MigrationInitialization ->
76
- initializeSchema con verbose >> return MigrationSuccess
84
+ initializeSchema con tableName verbose >> return MigrationSuccess
77
85
MigrationDirectory path ->
78
- executeDirectoryMigration con verbose path
86
+ executeDirectoryMigration con tableName verbose path
79
87
MigrationScript name contents ->
80
- executeMigration con verbose name contents
88
+ executeMigration con tableName verbose name contents
81
89
MigrationFile name path ->
82
- executeMigration con verbose name =<< BS. readFile path
90
+ executeMigration con tableName verbose name =<< BS. readFile path
83
91
MigrationValidation validationCmd ->
84
- executeValidation con verbose validationCmd
92
+ executeValidation con tableName verbose validationCmd
85
93
MigrationCommands commands ->
86
- runMigrations verbose con commands
94
+ runMigrations' verbose con commands tableName
87
95
88
96
-- | Execute a sequence of migrations
89
97
--
@@ -100,8 +108,27 @@ runMigrations
100
108
-> [MigrationCommand ]
101
109
-- ^ The commands to run
102
110
-> IO (MigrationResult String )
103
- runMigrations verbose con commands =
104
- sequenceMigrations [runMigration (MigrationContext c verbose con) | c <- commands]
111
+ runMigrations verbose con commands = runMigrations' verbose con commands " schema_migrations"
112
+
113
+ -- | Execute a sequence of migrations
114
+ --
115
+ -- Returns 'MigrationSuccess' if all of the provided 'MigrationCommand's
116
+ -- execute without error. If an error occurs, execution is stopped and the
117
+ -- 'MigrationError' is returned.
118
+ --
119
+ -- It is recommended to wrap 'runMigrations' inside a database transaction.
120
+ runMigrations'
121
+ :: Bool
122
+ -- ^ Run in verbose mode
123
+ -> Connection
124
+ -- ^ The postgres connection to use
125
+ -> [MigrationCommand ]
126
+ -- ^ The commands to run
127
+ -> BS. ByteString
128
+ -- ^ The schema_migrations table name
129
+ -> IO (MigrationResult String )
130
+ runMigrations' verbose con commands tableName =
131
+ sequenceMigrations [runMigration' (MigrationContext' c verbose con tableName) | c <- commands]
105
132
106
133
-- | Run a sequence of contexts, stopping on the first failure
107
134
sequenceMigrations :: Monad m => [m (MigrationResult e )] -> m (MigrationResult e )
@@ -115,12 +142,12 @@ sequenceMigrations = \case
115
142
116
143
-- | Executes all SQL-file based migrations located in the provided 'dir'
117
144
-- in alphabetical order.
118
- executeDirectoryMigration :: Connection -> Bool -> FilePath -> IO (MigrationResult String )
119
- executeDirectoryMigration con verbose dir =
145
+ executeDirectoryMigration :: Connection -> BS. ByteString -> Bool -> FilePath -> IO (MigrationResult String )
146
+ executeDirectoryMigration con tableName verbose dir =
120
147
scriptsInDirectory dir >>= go
121
148
where
122
149
go fs = sequenceMigrations (executeMigrationFile <$> fs)
123
- executeMigrationFile f = executeMigration con verbose f =<< BS. readFile (dir ++ " /" ++ f)
150
+ executeMigrationFile f = executeMigration con tableName verbose f =<< BS. readFile (dir ++ " /" ++ f)
124
151
125
152
-- | Lists all files in the given 'FilePath' 'dir' in alphabetical order.
126
153
scriptsInDirectory :: FilePath -> IO [String ]
@@ -130,10 +157,10 @@ scriptsInDirectory dir =
130
157
131
158
-- | Executes a generic SQL migration for the provided script 'name' with
132
159
-- content 'contents'.
133
- executeMigration :: Connection -> Bool -> ScriptName -> BS. ByteString -> IO (MigrationResult String )
134
- executeMigration con verbose name contents = do
160
+ executeMigration :: Connection -> BS. ByteString -> Bool -> ScriptName -> BS. ByteString -> IO (MigrationResult String )
161
+ executeMigration con tableName verbose name contents = do
135
162
let checksum = md5Hash contents
136
- checkScript con name checksum >>= \ case
163
+ checkScript con tableName name checksum >>= \ case
137
164
ScriptOk -> do
138
165
when verbose $ putStrLn $ " Ok:\t " ++ name
139
166
return MigrationSuccess
@@ -146,15 +173,15 @@ executeMigration con verbose name contents = do
146
173
when verbose $ putStrLn $ " Fail:\t " ++ name
147
174
return (MigrationError name)
148
175
where
149
- q = " insert into schema_migrations (filename, checksum) values(?, ?)"
176
+ q = " insert into " <> Query tableName <> " (filename, checksum) values(?, ?)"
150
177
151
178
-- | Initializes the database schema with a helper table containing
152
179
-- meta-information about executed migrations.
153
- initializeSchema :: Connection -> Bool -> IO ()
154
- initializeSchema con verbose = do
180
+ initializeSchema :: Connection -> BS. ByteString -> Bool -> IO ()
181
+ initializeSchema con tableName verbose = do
155
182
when verbose $ putStrLn " Initializing schema"
156
183
void $ execute_ con $ mconcat
157
- [ " create table if not exists schema_migrations "
184
+ [ " create table if not exists " <> Query tableName <> " "
158
185
, " ( filename varchar(512) not null"
159
186
, " , checksum varchar(32) not null"
160
187
, " , executed_at timestamp without time zone not null default now() "
@@ -172,12 +199,14 @@ initializeSchema con verbose = do
172
199
-- * 'MigrationFile': validate the presence and checksum of the given file.
173
200
-- * 'MigrationValidation': always succeeds.
174
201
-- * 'MigrationCommands': validates all the sub-commands stopping at the first failure.
175
- executeValidation :: Connection -> Bool -> MigrationCommand -> IO (MigrationResult String )
176
- executeValidation con verbose cmd = case cmd of
202
+ executeValidation :: Connection -> BS. ByteString -> Bool -> MigrationCommand -> IO (MigrationResult String )
203
+ executeValidation con tableName' verbose cmd =
204
+ let tableName = BS8. unpack tableName' in
205
+ case cmd of
177
206
MigrationInitialization ->
178
- existsTable con " schema_migrations " >>= \ r -> return $ if r
207
+ existsTable con tableName >>= \ r -> return $ if r
179
208
then MigrationSuccess
180
- else MigrationError " No such table: schema_migrations "
209
+ else MigrationError $ " No such table: " <> tableName
181
210
MigrationDirectory path ->
182
211
scriptsInDirectory path >>= goScripts path
183
212
MigrationScript name contents ->
@@ -187,10 +216,10 @@ executeValidation con verbose cmd = case cmd of
187
216
MigrationValidation _ ->
188
217
return MigrationSuccess
189
218
MigrationCommands cs ->
190
- sequenceMigrations (executeValidation con verbose <$> cs)
219
+ sequenceMigrations (executeValidation con tableName' verbose <$> cs)
191
220
where
192
221
validate name contents =
193
- checkScript con name (md5Hash contents) >>= \ case
222
+ checkScript con tableName' name (md5Hash contents) >>= \ case
194
223
ScriptOk -> do
195
224
when verbose $ putStrLn $ " Ok:\t " ++ name
196
225
return MigrationSuccess
@@ -209,8 +238,8 @@ executeValidation con verbose cmd = case cmd of
209
238
-- is compared against the one that was executed.
210
239
-- If there is no matching script entry in the database, the script
211
240
-- will be executed and its meta-information will be recorded.
212
- checkScript :: Connection -> ScriptName -> Checksum -> IO CheckScriptResult
213
- checkScript con name checksum =
241
+ checkScript :: Connection -> BS. ByteString -> ScriptName -> Checksum -> IO CheckScriptResult
242
+ checkScript con tableName name checksum =
214
243
query con q (Only name) >>= \ case
215
244
[] ->
216
245
return ScriptNotExecuted
@@ -220,7 +249,7 @@ checkScript con name checksum =
220
249
return (ScriptModified actualChecksum)
221
250
where
222
251
q = mconcat
223
- [ " select checksum from schema_migrations "
252
+ [ " select checksum from " <> Query tableName <> " "
224
253
, " where filename = ? limit 1"
225
254
]
226
255
@@ -289,20 +318,32 @@ data MigrationResult a
289
318
290
319
-- | The 'MigrationContext' provides an execution context for migrations.
291
320
data MigrationContext = MigrationContext
292
- { migrationContextCommand :: MigrationCommand
321
+ { migrationContextCommand :: ! MigrationCommand
322
+ -- ^ The action that will be performed by 'runMigration'
323
+ , migrationContextVerbose :: ! Bool
324
+ -- ^ Verbosity of the library.
325
+ , migrationContextConnection :: ! Connection
326
+ -- ^ The PostgreSQL connection to use for migrations.
327
+ }
328
+
329
+ -- | The 'MigrationContext'' provides an execution context for migrations, with additional options to MigrationContext
330
+ data MigrationContext' = MigrationContext'
331
+ { migrationContextCommand' :: ! MigrationCommand
293
332
-- ^ The action that will be performed by 'runMigration'
294
- , migrationContextVerbose :: Bool
333
+ , migrationContextVerbose' :: ! Bool
295
334
-- ^ Verbosity of the library.
296
- , migrationContextConnection :: Connection
335
+ , migrationContextConnection' :: ! Connection
297
336
-- ^ The PostgreSQL connection to use for migrations.
337
+ , migrationTableName :: ! BS. ByteString
338
+ -- ^ The name of the table that stores the migrations
298
339
}
299
340
300
341
-- | Produces a list of all executed 'SchemaMigration's.
301
- getMigrations :: Connection -> IO [SchemaMigration ]
302
- getMigrations = flip query_ q
342
+ getMigrations :: Connection -> BS. ByteString -> IO [SchemaMigration ]
343
+ getMigrations con tableName = query_ con q
303
344
where q = mconcat
304
345
[ " select filename, checksum, executed_at "
305
- , " from schema_migrations order by executed_at asc"
346
+ , " from " <> Query tableName <> " order by executed_at asc"
306
347
]
307
348
308
349
-- | A product type representing a single, executed 'SchemaMigration'.
0 commit comments