|
5 | 5 | // Created by Wes Wickwire on 2/16/25. |
6 | 6 | // |
7 | 7 |
|
8 | | -public enum MigrationRunner { |
| 8 | +/// Executes the migrations and ensures it is up to date. |
| 9 | +enum MigrationRunner { |
9 | 10 | static let migrationTableName = "__otterMigrations" |
10 | | - |
11 | | - public static func execute(migrations: [String], pool: ConnectionPool) async throws { |
12 | | - try await pool.begin(.write) { tx in |
13 | | - try execute(migrations: migrations, tx: tx) |
14 | | - } |
15 | | - } |
16 | | - |
17 | | - public static func execute(migrations: [String], tx: borrowing Transaction) throws { |
18 | | - try createTableIfNeeded(tx: tx) |
19 | | - |
20 | | - let lastMigration = try lastMigration(tx: tx) |
| 11 | + |
| 12 | + static func execute(migrations: [String], connection: SQLiteConnection) throws { |
| 13 | + let previouslyRunMigrations = try runMigrations(connection: connection) |
| 14 | + let lastMigration = previouslyRunMigrations.last ?? Int.min |
21 | 15 |
|
22 | 16 | let pendingMigrations = migrations.enumerated() |
23 | | - .map { (number: $0.offset + 1, migration: $0.element) } |
| 17 | + .map { (number: $0.offset, migration: $0.element) } |
24 | 18 | .filter { $0.number > lastMigration } |
25 | | - .sorted { $0.number < $1.number } |
26 | 19 |
|
27 | 20 | for (number, migration) in pendingMigrations { |
28 | | - try execute(migration: migration, number: number, tx: tx) |
| 21 | + // Run each migration in it's own transaction. |
| 22 | + let tx = try Transaction(connection: connection, kind: .write) |
| 23 | + |
| 24 | + let result = Result { |
| 25 | + try execute(migration: migration, number: number, tx: tx) |
| 26 | + } |
| 27 | + |
| 28 | + switch result { |
| 29 | + case .success: |
| 30 | + try tx.commit() |
| 31 | + case .failure(let error): |
| 32 | + try tx.commitOrRollback() |
| 33 | + throw error |
| 34 | + } |
29 | 35 | } |
30 | 36 | } |
31 | 37 |
|
32 | | - static func createTableIfNeeded(tx: borrowing Transaction) throws(OtterError) { |
33 | | - try tx.execute(sql: """ |
34 | | - CREATE TABLE IF NOT EXISTS \(migrationTableName)( |
35 | | - number INTEGER PRIMARY KEY |
36 | | - ) STRICT; |
37 | | - """) |
38 | | - } |
39 | | - |
40 | | - /// Executes the migration, if the `number` exists it will be recorded in the migrations table. |
41 | | - static func execute(migration: String, number: Int?, tx: borrowing Transaction) throws { |
| 38 | + /// Executes the migration, if the `number` exists it will be |
| 39 | + /// recorded in the migrations table. |
| 40 | + private static func execute( |
| 41 | + migration: String, |
| 42 | + number: Int?, |
| 43 | + tx: borrowing Transaction |
| 44 | + ) throws { |
42 | 45 | try tx.execute(sql: migration) |
43 | 46 |
|
44 | 47 | if let number { |
45 | 48 | try insertMigration(version: number, tx: tx) |
46 | 49 | } |
47 | 50 | } |
48 | 51 |
|
49 | | - private static func lastMigration(tx: borrowing Transaction) throws -> Int { |
| 52 | + /// Creates the migrations table and gets the last migration that ran. |
| 53 | + private static func runMigrations(connection: SQLiteConnection) throws -> [Int] { |
| 54 | + let tx = try Transaction(connection: connection, kind: .write) |
| 55 | + |
| 56 | + // Create the migration table if need be. |
| 57 | + try tx.execute(sql: """ |
| 58 | + CREATE TABLE IF NOT EXISTS \(migrationTableName)( |
| 59 | + number INTEGER PRIMARY KEY |
| 60 | + ) STRICT; |
| 61 | + """) |
| 62 | + |
50 | 63 | let statement = try Statement(in: tx) { |
51 | | - "SELECT MAX(number) FROM \(MigrationRunner.migrationTableName)" |
| 64 | + "SELECT * FROM \(MigrationRunner.migrationTableName) ORDER BY number ASC" |
52 | 65 | } |
53 | 66 |
|
54 | | - return try statement.fetchOne(of: Int.self) ?? 0 |
| 67 | + try tx.commit() |
| 68 | + return try statement.fetchAll() |
55 | 69 | } |
56 | 70 |
|
57 | 71 | private static func insertMigration(version: Int, tx: borrowing Transaction) throws { |
|
0 commit comments