Skip to content

Commit 99f40ae

Browse files
authored
support for additional database migrations (#1644)
1 parent 3a9381b commit 99f40ae

File tree

9 files changed

+72
-73
lines changed

9 files changed

+72
-73
lines changed

src/Simplex/Messaging/Agent.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2516,7 +2516,7 @@ execAgentStoreSQL :: AgentClient -> Text -> AE [Text]
25162516
execAgentStoreSQL c sql = withAgentEnv c $ withStore' c (`execSQL` sql)
25172517

25182518
getAgentMigrations :: AgentClient -> AE [UpMigration]
2519-
getAgentMigrations c = withAgentEnv c $ map upMigration <$> withStore' c getCurrentMigrations
2519+
getAgentMigrations c = withAgentEnv c $ map upMigration <$> withStore' c (getCurrentMigrations Nothing)
25202520

25212521
debugAgentLocks :: AgentClient -> IO AgentLocks
25222522
debugAgentLocks AgentClient {connLocks = cs, invLocks = is, deleteLock = d} = do

src/Simplex/Messaging/Agent/Store/Postgres.hs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module Simplex.Messaging.Agent.Store.Postgres
88
( DBOpts (..),
99
Migrations.getCurrentMigrations,
1010
checkSchemaExists,
11+
migrateDBSchema,
1112
createDBStore,
1213
closeDBStore,
1314
reopenDBStore,
@@ -38,18 +39,20 @@ import System.Exit (exitFailure)
3839
-- If passed schema does not exist in connectInfo database, it will be created.
3940
-- Applies necessary migrations to schema.
4041
createDBStore :: DBOpts -> [Migration] -> MigrationConfig -> IO (Either MigrationError DBStore)
41-
createDBStore opts migrations MigrationConfig {confirm} = do
42+
createDBStore opts migrations migrationConfig = do
4243
st <- connectPostgresStore opts
43-
r <- migrateSchema st `onException` closeDBStore st
44+
r <- migrateDBSchema st opts Nothing migrations migrationConfig `onException` closeDBStore st
4445
case r of
4546
Right () -> pure $ Right st
4647
Left e -> closeDBStore st $> Left e
47-
where
48-
migrateSchema st =
49-
let initialize = Migrations.initialize st
50-
getCurrent = withTransaction st Migrations.getCurrentMigrations
51-
dbm = DBMigrate {initialize, getCurrent, run = Migrations.run st, backup = Nothing}
52-
in sharedMigrateSchema dbm (dbNew st) migrations confirm
48+
49+
migrateDBSchema :: DBStore -> DBOpts -> Maybe Query -> [Migration] -> MigrationConfig -> IO (Either MigrationError ())
50+
migrateDBSchema st _opts migrationsTable migrations MigrationConfig {confirm} =
51+
let initialize = Migrations.initialize st migrationsTable
52+
getCurrent = withTransaction st $ Migrations.getCurrentMigrations migrationsTable
53+
run = Migrations.run st migrationsTable
54+
dbm = DBMigrate {initialize, getCurrent, run, backup = Nothing}
55+
in sharedMigrateSchema dbm (dbNew st) migrations confirm
5356

5457
connectPostgresStore :: DBOpts -> IO DBStore
5558
connectPostgresStore DBOpts {connstr, schema, poolSize, createSchema} = do

src/Simplex/Messaging/Agent/Store/Postgres/Migrations.hs

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,55 +14,50 @@ where
1414
import Control.Exception (throwIO)
1515
import Control.Monad (void)
1616
import qualified Data.ByteString.Char8 as B
17+
import Data.Maybe (fromMaybe)
1718
import qualified Data.Text as T
1819
import qualified Data.Text.Encoding as TE
1920
import Data.Time.Clock (getCurrentTime)
2021
import qualified Database.PostgreSQL.LibPQ as LibPQ
21-
import Database.PostgreSQL.Simple (Only (..))
22+
import Database.PostgreSQL.Simple (Only (..), Query)
2223
import qualified Database.PostgreSQL.Simple as PSQL
2324
import Database.PostgreSQL.Simple.Internal (Connection (..))
24-
import Database.PostgreSQL.Simple.SqlQQ (sql)
2525
import Simplex.Messaging.Agent.Store.Postgres.Common
2626
import Simplex.Messaging.Agent.Store.Shared
2727
import Simplex.Messaging.Util (($>>=))
2828
import UnliftIO.MVar
2929

30-
initialize :: DBStore -> IO ()
31-
initialize st = withTransaction' st $ \db ->
32-
void $
33-
PSQL.execute_
34-
db
35-
[sql|
36-
CREATE TABLE IF NOT EXISTS migrations (
37-
name TEXT NOT NULL,
38-
ts TIMESTAMP NOT NULL,
39-
down TEXT,
40-
PRIMARY KEY (name)
41-
)
42-
|]
30+
initialize :: DBStore -> Maybe Query -> IO ()
31+
initialize st migrationsTable = withTransaction' st $ \db ->
32+
void $ PSQL.execute_ db $
33+
"CREATE TABLE IF NOT EXISTS "
34+
<> fromMaybe "migrations" migrationsTable
35+
<> " (name TEXT NOT NULL PRIMARY KEY, ts TIMESTAMP NOT NULL, down TEXT)"
4336

44-
run :: DBStore -> MigrationsToRun -> IO ()
45-
run st = \case
37+
run :: DBStore -> Maybe Query -> MigrationsToRun -> IO ()
38+
run st migrationsTable = \case
4639
MTRUp [] -> pure ()
4740
MTRUp ms -> mapM_ runUp ms
4841
MTRDown ms -> mapM_ runDown $ reverse ms
4942
MTRNone -> pure ()
5043
where
44+
table = fromMaybe "migrations" migrationsTable
5145
runUp Migration {name, up, down} = withTransaction' st $ \db -> do
5246
insert db
5347
execSQL db up
5448
where
55-
insert db = void $ PSQL.execute db "INSERT INTO migrations (name, down, ts) VALUES (?,?,?)" . (name,down,) =<< getCurrentTime
49+
insert db = void $ PSQL.execute db ("INSERT INTO " <> table <> " (name, down, ts) VALUES (?,?,?)") . (name,down,) =<< getCurrentTime
5650
runDown DownMigration {downName, downQuery} = withTransaction' st $ \db -> do
5751
execSQL db downQuery
58-
void $ PSQL.execute db "DELETE FROM migrations WHERE name = ?" (Only downName)
52+
void $ PSQL.execute db ("DELETE FROM " <> table <> " WHERE name = ?") (Only downName)
5953
execSQL db query =
6054
withMVar (connectionHandle db) $ \pqConn ->
6155
LibPQ.exec pqConn (TE.encodeUtf8 query) $>>= LibPQ.resultErrorMessage >>= \case
6256
Just e | not (B.null e) -> throwIO $ userError $ B.unpack e
6357
_ -> pure ()
6458

65-
getCurrentMigrations :: PSQL.Connection -> IO [Migration]
66-
getCurrentMigrations db = map toMigration <$> PSQL.query_ db "SELECT name, down FROM migrations ORDER BY name ASC;"
59+
getCurrentMigrations :: Maybe Query -> PSQL.Connection -> IO [Migration]
60+
getCurrentMigrations migrationsTable db = map toMigration <$> PSQL.query_ db ("SELECT name, down FROM " <> table <> " ORDER BY name ASC;")
6761
where
62+
table = fromMaybe "migrations" migrationsTable
6863
toMigration (name, down) = Migration {name, up = T.pack "", down}

src/Simplex/Messaging/Agent/Store/SQLite.hs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
module Simplex.Messaging.Agent.Store.SQLite
2828
( DBOpts (..),
2929
Migrations.getCurrentMigrations,
30+
migrateDBSchema,
3031
createDBStore,
3132
closeDBStore,
3233
reopenDBStore,
@@ -68,25 +69,27 @@ import UnliftIO.STM
6869
-- * SQLite Store implementation
6970

7071
createDBStore :: DBOpts -> [Migration] -> MigrationConfig -> IO (Either MigrationError DBStore)
71-
createDBStore DBOpts {dbFilePath, dbKey, keepKey, track, vacuum} migrations MigrationConfig {confirm, backupPath} = do
72+
createDBStore opts@DBOpts {dbFilePath, dbKey, keepKey, track} migrations migrationConfig = do
7273
let dbDir = takeDirectory dbFilePath
7374
createDirectoryIfMissing True dbDir
7475
st <- connectSQLiteStore dbFilePath dbKey keepKey track
75-
r <- migrateSchema st `onException` closeDBStore st
76+
r <- migrateDBSchema st opts Nothing migrations migrationConfig `onException` closeDBStore st
7677
case r of
7778
Right () -> pure $ Right st
7879
Left e -> closeDBStore st $> Left e
7980
where
80-
migrateSchema st =
81-
let initialize = Migrations.initialize st
82-
getCurrent = withTransaction st Migrations.getCurrentMigrations
83-
run = Migrations.run st vacuum
84-
backup = mkBackup <$> backupPath
85-
mkBackup bp =
86-
let f = if null bp then dbFilePath else bp </> takeFileName dbFilePath
87-
in copyFile dbFilePath $ f <> ".bak"
88-
dbm = DBMigrate {initialize, getCurrent, run, backup}
89-
in sharedMigrateSchema dbm (dbNew st) migrations confirm
81+
82+
migrateDBSchema :: DBStore -> DBOpts -> Maybe Query -> [Migration] -> MigrationConfig -> IO (Either MigrationError ())
83+
migrateDBSchema st DBOpts {dbFilePath, vacuum} migrationsTable migrations MigrationConfig {confirm, backupPath} =
84+
let initialize = Migrations.initialize st migrationsTable
85+
getCurrent = withTransaction st $ Migrations.getCurrentMigrations migrationsTable
86+
run = Migrations.run st migrationsTable vacuum
87+
backup = mkBackup <$> backupPath
88+
mkBackup bp =
89+
let f = if null bp then dbFilePath else bp </> takeFileName dbFilePath
90+
in copyFile dbFilePath $ f <> ".bak"
91+
dbm = DBMigrate {initialize, getCurrent, run, backup}
92+
in sharedMigrateSchema dbm (dbNew st) migrations confirm
9093

9194
connectSQLiteStore :: FilePath -> ScrubbedBytes -> Bool -> DB.TrackQueries -> IO DBStore
9295
connectSQLiteStore dbFilePath key keepKey track = do

src/Simplex/Messaging/Agent/Store/SQLite/Migrations.hs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ where
1717
import Control.Monad (forM_, when)
1818
import Data.List.NonEmpty (NonEmpty)
1919
import qualified Data.Map.Strict as M
20+
import Data.Maybe (fromMaybe)
2021
import Data.Text (Text)
2122
import Data.Text.Encoding (decodeLatin1)
2223
import Data.Time.Clock (getCurrentTime)
2324
import Database.SQLite.Simple (Only (..), Query (..))
2425
import qualified Database.SQLite.Simple as SQL
25-
import Database.SQLite.Simple.QQ (sql)
2626
import qualified Database.SQLite3 as SQLite3
2727
import Simplex.Messaging.Agent.Protocol (extraSMPServerHosts)
2828
import qualified Simplex.Messaging.Agent.Store.DB as DB
@@ -32,25 +32,29 @@ import Simplex.Messaging.Agent.Store.Shared
3232
import Simplex.Messaging.Encoding.String
3333
import Simplex.Messaging.Transport.Client (TransportHost)
3434

35-
getCurrentMigrations :: DB.Connection -> IO [Migration]
36-
getCurrentMigrations DB.Connection {DB.conn} = map toMigration <$> SQL.query_ conn "SELECT name, down FROM migrations ORDER BY name ASC;"
35+
getCurrentMigrations :: Maybe Query -> DB.Connection -> IO [Migration]
36+
getCurrentMigrations migrationsTable DB.Connection {DB.conn} =
37+
map toMigration
38+
<$> SQL.query_ conn ("SELECT name, down FROM " <> table <> " ORDER BY name ASC;")
3739
where
40+
table = fromMaybe "migrations" migrationsTable
3841
toMigration (name, down) = Migration {name, up = "", down}
3942

40-
run :: DBStore -> Bool -> MigrationsToRun -> IO ()
41-
run st vacuum = \case
43+
run :: DBStore -> Maybe Query -> Bool -> MigrationsToRun -> IO ()
44+
run st migrationsTable vacuum = \case
4245
MTRUp [] -> pure ()
4346
MTRUp ms -> do
4447
mapM_ runUp ms
4548
when vacuum $ withConnection' st (`execSQL` "VACUUM;")
4649
MTRDown ms -> mapM_ runDown $ reverse ms
4750
MTRNone -> pure ()
4851
where
52+
table = fromMaybe "migrations" migrationsTable
4953
runUp Migration {name, up, down} = withTransaction' st $ \db -> do
5054
when (name == "m20220811_onion_hosts") $ updateServers db
5155
insert db >> execSQL db up'
5256
where
53-
insert db = SQL.execute db "INSERT INTO migrations (name, down, ts) VALUES (?,?,?)" . (name,down,) =<< getCurrentTime
57+
insert db = SQL.execute db ("INSERT INTO " <> table <> " (name, down, ts) VALUES (?,?,?)") . (name,down,) =<< getCurrentTime
5458
up'
5559
| dbNew st && name == "m20230110_users" = fromQuery new_m20230110_users
5660
| otherwise = up
@@ -59,24 +63,19 @@ run st vacuum = \case
5963
in SQL.execute db "UPDATE servers SET host = ? WHERE host = ?" (hs, decodeLatin1 $ strEncode h)
6064
runDown DownMigration {downName, downQuery} = withTransaction' st $ \db -> do
6165
execSQL db downQuery
62-
SQL.execute db "DELETE FROM migrations WHERE name = ?" (Only downName)
66+
SQL.execute db ("DELETE FROM " <> table <> " WHERE name = ?") (Only downName)
6367
execSQL db = SQLite3.exec $ SQL.connectionHandle db
6468

65-
initialize :: DBStore -> IO ()
66-
initialize st = withTransaction' st $ \db -> do
67-
cs :: [Text] <- map fromOnly <$> SQL.query_ db "SELECT name FROM pragma_table_info('migrations')"
69+
initialize :: DBStore -> Maybe Query -> IO ()
70+
initialize st migrationsTable = withTransaction' st $ \db -> do
71+
cs :: [Text] <- map fromOnly <$> SQL.query_ db ("SELECT name FROM pragma_table_info('" <> table <> "')")
6872
case cs of
6973
[] -> createMigrations db
70-
_ -> when ("down" `notElem` cs) $ SQL.execute_ db "ALTER TABLE migrations ADD COLUMN down TEXT"
74+
_ -> when ("down" `notElem` cs) $ SQL.execute_ db $ "ALTER TABLE " <> table <> " ADD COLUMN down TEXT"
7175
where
76+
table = fromMaybe "migrations" migrationsTable
7277
createMigrations db =
73-
SQL.execute_
74-
db
75-
[sql|
76-
CREATE TABLE IF NOT EXISTS migrations (
77-
name TEXT NOT NULL,
78-
ts TEXT NOT NULL,
79-
down TEXT,
80-
PRIMARY KEY (name)
81-
);
82-
|]
78+
SQL.execute_ db $
79+
"CREATE TABLE IF NOT EXISTS "
80+
<> table
81+
<> " (name TEXT NOT NULL PRIMARY KEY, ts TEXT NOT NULL, down TEXT)"

src/Simplex/Messaging/Agent/Store/SQLite/Migrations/agent_schema.sql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
CREATE TABLE migrations(
2-
name TEXT NOT NULL,
2+
name TEXT NOT NULL PRIMARY KEY,
33
ts TEXT NOT NULL,
4-
down TEXT,
5-
PRIMARY KEY(name)
4+
down TEXT
65
);
76
CREATE TABLE servers(
87
host TEXT NOT NULL,

tests/AgentTests/SQLiteTests.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ testReopenEncryptedStoreKeepKey = do
642642
hasMigrations st
643643

644644
getMigrations :: DBStore -> IO Bool
645-
getMigrations st = not . null <$> withTransaction st getCurrentMigrations
645+
getMigrations st = not . null <$> withTransaction st (getCurrentMigrations Nothing)
646646

647647
hasMigrations :: DBStore -> Expectation
648648
hasMigrations st = getMigrations st `shouldReturn` True

tests/AgentTests/SchemaDump.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ testSchemaMigrations = do
7676
putStrLn $ "down migration " <> name m
7777
let downMigr = fromJust $ toDownMigration m
7878
schema <- getSchema testDB testSchema
79-
Migrations.run st True $ MTRUp [m]
79+
Migrations.run st Nothing True $ MTRUp [m]
8080
schema' <- getSchema testDB testSchema
8181
schema' `shouldNotBe` schema
82-
Migrations.run st True $ MTRDown [downMigr]
82+
Migrations.run st Nothing True $ MTRDown [downMigr]
8383
unless (name m `elem` skipComparisonForDownMigrations) $ do
8484
schema'' <- getSchema testDB testSchema
8585
schema'' `shouldBe` schema
86-
Migrations.run st True $ MTRUp [m]
86+
Migrations.run st Nothing True $ MTRUp [m]
8787
schema''' <- getSchema testDB testSchema
8888
schema''' `shouldBe` schema'
8989

tests/PostgresSchemaDump.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ postgresSchemaDumpTest migrations skipComparisonForDownMigrations testDBOpts@DBO
4444
putStrLn $ "down migration " <> name m
4545
let downMigr = fromJust $ toDownMigration m
4646
schema <- getSchema testSchemaPath
47-
Migrations.run st $ MTRUp [m]
47+
Migrations.run st Nothing $ MTRUp [m]
4848
schema' <- getSchema testSchemaPath
4949
schema' `shouldNotBe` schema
50-
Migrations.run st $ MTRDown [downMigr]
50+
Migrations.run st Nothing $ MTRDown [downMigr]
5151
unless (name m `elem` skipComparisonForDownMigrations) $ do
5252
schema'' <- getSchema testSchemaPath
5353
schema'' `shouldBe` schema
54-
Migrations.run st $ MTRUp [m]
54+
Migrations.run st Nothing $ MTRUp [m]
5555
schema''' <- getSchema testSchemaPath
5656
schema''' `shouldBe` schema'
5757

0 commit comments

Comments
 (0)