Skip to content

Commit de632fd

Browse files
committed
Always migration
1 parent 97e920e commit de632fd

9 files changed

Lines changed: 110 additions & 14 deletions

File tree

Sources/Compiler/Compiler.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,8 @@ fileprivate struct CompilerWithSource {
158158
isReadOnly: isReadOnly,
159159
sanitizedSource: sanitizedSource,
160160
sourceSegments: sourceSegments,
161-
usedTableNames: typeChecker.usedTableNames
161+
usedTableNames: typeChecker.usedTableNames,
162+
syntax: stmt
162163
)
163164

164165
return (statement, typeChecker.allDiagnostics)

Sources/Compiler/Driver.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public actor Driver {
3131
case alwaysMigration
3232
}
3333

34+
enum Error: Swift.Error {
35+
case invalidMigrationName(String)
36+
case canOnlyHaveOneAlwaysMigration
37+
}
38+
3439
init(fileSystem: FileSystem) {
3540
self.fileSystem = fileSystem
3641
}
@@ -47,14 +52,21 @@ public actor Driver {
4752
let migrationsPath = migrationsPath(at: path)
4853
let queriesPath = queriesPath(at: path)
4954

50-
let migrationFiles = try fileSystem.files(atPath: migrationsPath)
55+
let (migrationFiles, alwaysMigration) = try organizeMigrations(
56+
fileNames: fileSystem.files(atPath: migrationsPath)
57+
)
5158
let queriesFiles = try fileSystem.files(atPath: queriesPath)
5259

5360
// Migrations must be run synchronously in order.
5461
for migration in migrationFiles {
5562
try compile(file: migration, in: migrationsPath, usage: .migration)
5663
}
5764

65+
// Always migrations always run after
66+
if let alwaysMigration {
67+
try compile(file: alwaysMigration, in: migrationsPath, usage: .migration)
68+
}
69+
5870
// Queries can be compiled independently
5971
try await withThrowingTaskGroup(of: Void.self) { group in
6072
for query in queriesFiles {
@@ -82,8 +94,15 @@ public actor Driver {
8294
.filter{ $0.usage == .queries }
8395
.map { ($0.fileName.split(separator: ".").first?.description, $0.statements) }
8496

97+
let alwaysMigration = results.values
98+
.first { $0.usage == .alwaysMigration }?
99+
.statements
100+
.map(\.sanitizedSource)
101+
.joined()
102+
85103
let file = try Lang.generate(
86104
migrations: migrations,
105+
alwaysMigration: alwaysMigration,
87106
queries: queries,
88107
schema: currentSchema,
89108
options: options
@@ -152,4 +171,36 @@ public actor Driver {
152171
private func queriesPath(at base: Path) -> Path {
153172
"\(base)/Queries"
154173
}
174+
175+
private func organizeMigrations(
176+
fileNames: [String]
177+
) throws -> (standard: [String], always: String?) {
178+
var standard: [String] = []
179+
var always: String?
180+
181+
for fileName in fileNames.sorted() {
182+
var components = fileName.split(separator: ".")
183+
184+
guard components.count == 2 else {
185+
throw Error.invalidMigrationName(fileName)
186+
}
187+
188+
let nameWithoutExt = components[0]
189+
190+
if Int(nameWithoutExt) != nil {
191+
standard.append(fileName)
192+
} else if nameWithoutExt == "Always" {
193+
guard always == nil else {
194+
// Can this really even happen?
195+
throw Error.canOnlyHaveOneAlwaysMigration
196+
}
197+
198+
always = fileName
199+
} else {
200+
throw Error.invalidMigrationName(fileName)
201+
}
202+
}
203+
204+
return (standard, always)
205+
}
155206
}

Sources/Compiler/Gen/Language.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public protocol Language {
2525
/// A file source code containing all of the generated tables, queries and migrations.
2626
static func file(
2727
migrations: [String],
28+
alwaysMigration: String?,
2829
tables: [GeneratedModel],
2930
queries: [(String?, [GeneratedQuery])],
3031
options: GenerationOptions
@@ -44,6 +45,7 @@ public protocol Language {
4445
extension Language {
4546
public static func generate(
4647
migrations: [String],
48+
alwaysMigration: String?,
4749
queries: [(String?, [Statement])],
4850
schema: Schema,
4951
options: GenerationOptions
@@ -52,6 +54,7 @@ extension Language {
5254

5355
return try file(
5456
migrations: migrations,
57+
alwaysMigration: alwaysMigration,
5558
tables: values.tables,
5659
queries: values.queries,
5760
options: options

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public struct SwiftLanguage: Language {
5858

5959
public static func file(
6060
migrations: [String],
61+
alwaysMigration: String?,
6162
tables: [GeneratedModel],
6263
queries: [(String?, [GeneratedQuery])],
6364
options: GenerationOptions
@@ -95,6 +96,10 @@ public struct SwiftLanguage: Language {
9596

9697
try declaration(for: migrations, options: options)
9798

99+
if let alwaysMigration {
100+
try declaration(alwaysMigration: alwaysMigration, options: options)
101+
}
102+
98103
for (namespace, queries) in queries {
99104
if let namespace {
100105
try queriesVariable(name: namespace, queries: queries)
@@ -203,6 +208,18 @@ public struct SwiftLanguage: Language {
203208
return DeclSyntax(variable)
204209
}
205210

211+
/// The migrations variable
212+
private static func declaration(
213+
alwaysMigration: String,
214+
options: GenerationOptions
215+
) throws -> DeclSyntax {
216+
let variable = try VariableDeclSyntax("static var alwaysMigration: String?") {
217+
stringLiteral(of: alwaysMigration, multiline: true)
218+
}
219+
220+
return DeclSyntax(variable)
221+
}
222+
206223
/// Generates the expression to initialize the query.
207224
///
208225
/// ```swift

Sources/Compiler/Sema/Statement.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public struct Statement {
2424
public let sourceSegments: [SourceSegment]
2525
/// Any table that were accessed and used in the query.
2626
public let usedTableNames: Set<Substring>
27+
/// The syntax associated to the statement
28+
let syntax: any StmtSyntax
2729

2830
/// If `true` the query returns nothing.
2931
public var noOutput: Bool {
@@ -45,7 +47,8 @@ public struct Statement {
4547
isReadOnly: isReadOnly,
4648
sanitizedSource: sanitizedSource,
4749
sourceSegments: sourceSegments,
48-
usedTableNames: usedTableNames
50+
usedTableNames: usedTableNames,
51+
syntax: syntax
4952
)
5053
}
5154
}

Sources/Feather/ConnectionPool.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public actor ConnectionPool: Sendable {
3636
public init(
3737
path: String,
3838
limit: Int,
39-
migrations: [String]
39+
migrations: [String],
40+
alwaysMigration: String? = nil
4041
) throws {
4142
guard limit > 0 else {
4243
throw FeatherError.poolCannotHaveZeroConnections
@@ -52,7 +53,7 @@ public actor ConnectionPool: Sendable {
5253
try connection.execute(sql: "PRAGMA journal_mode=WAL;")
5354

5455
let tx = try Transaction(connection: connection, kind: .write)
55-
try MigrationRunner.execute(migrations: migrations, tx: tx)
56+
try MigrationRunner.execute(migrations: migrations, alwaysMigration: alwaysMigration, tx: tx)
5657
try tx.commit()
5758

5859
self.availableConnections = [connection]

Sources/Feather/Database.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
public protocol Database {
99
init(connection: any Connection)
1010
static var migrations: [String] { get }
11+
static var alwaysMigration: String? { get }
1112
}
1213

1314
public extension Database {
@@ -16,21 +17,24 @@ public extension Database {
1617
try ConnectionPool(
1718
path: path,
1819
limit: config.maxConnectionCount,
19-
migrations: Self.migrations
20+
migrations: Self.migrations,
21+
alwaysMigration: Self.alwaysMigration
2022
)
2123
} else {
2224
try ConnectionPool(
2325
path: ":memory:",
2426
limit: 1,
25-
migrations: Self.migrations
27+
migrations: Self.migrations,
28+
alwaysMigration: Self.alwaysMigration
2629
)
2730
}
2831

2932
self = Self(connection: connection)
3033
}
3134

35+
static var alwaysMigration: String? { nil }
36+
3237
static func inMemory() throws -> Self {
3338
return try Self(config: DatabaseConfig(path: nil))
3439
}
3540
}
36-

Sources/Feather/Migration.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,21 @@
88
public struct MigrationRunner {
99
static let migrationTableName = "__featherMigrations"
1010

11-
public static func execute(migrations: [String], pool: ConnectionPool) async throws {
11+
public static func execute(
12+
migrations: [String],
13+
alwaysMigration: String?,
14+
pool: ConnectionPool
15+
) async throws {
1216
try await pool.begin(.write) { tx in
13-
try execute(migrations: migrations, tx: tx)
17+
try execute(migrations: migrations, alwaysMigration: alwaysMigration, tx: tx)
1418
}
1519
}
1620

17-
public static func execute(migrations: [String], tx: borrowing Transaction) throws {
21+
public static func execute(
22+
migrations: [String],
23+
alwaysMigration: String?,
24+
tx: borrowing Transaction
25+
) throws {
1826
try createTableIfNeeded(tx: tx)
1927

2028
let lastMigration = try lastMigration(tx: tx)
@@ -27,6 +35,10 @@ public struct MigrationRunner {
2735
for (number, migration) in pendingMigrations {
2836
try execute(migration: migration, number: number, tx: tx)
2937
}
38+
39+
if let alwaysMigration {
40+
try execute(migration: alwaysMigration, number: nil, tx: tx)
41+
}
3042
}
3143

3244
static func createTableIfNeeded(tx: borrowing Transaction) throws(FeatherError) {
@@ -37,9 +49,13 @@ public struct MigrationRunner {
3749
""")
3850
}
3951

40-
static func execute(migration: String, number: Int, tx: borrowing Transaction) throws {
52+
/// Executes the migration, if the `number` exists it will be recorded in the migrations table.
53+
static func execute(migration: String, number: Int?, tx: borrowing Transaction) throws {
4154
try tx.execute(sql: migration)
42-
try insertMigration(version: number, tx: tx)
55+
56+
if let number {
57+
try insertMigration(version: number, tx: tx)
58+
}
4359
}
4460

4561
private static func lastMigration(tx: borrowing Transaction) throws -> Int {

Sources/FeatherMacros/DatabaseMacro.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ extension DatabaseMacro: MemberMacro {
6464
databaseName: structDecl.name.text,
6565
tables: generatedTables,
6666
queries: generatedQueries.flatMap(\.1),
67-
options: [],
67+
options: GenerationOptions(),
6868
addConnection: variables["connection"] == nil
6969
)
7070
}

0 commit comments

Comments
 (0)