import Foundation import GRDB /// AppDatabase lets the application access the database. /// /// It applies the pratices recommended at /// struct AppDatabase { /// Creates an `AppDatabase`, and make sure the database schema is ready. init(_ dbWriter: any DatabaseWriter) throws { self.dbWriter = dbWriter try migrator.migrate(dbWriter) } /// Provides access to the database. /// /// Application can use a `DatabasePool`, while SwiftUI previews and tests /// can use a fast in-memory `DatabaseQueue`. /// /// See private let dbWriter: any DatabaseWriter /// The DatabaseMigrator that defines the database schema. /// /// See private var migrator: DatabaseMigrator { var migrator = DatabaseMigrator() #if DEBUG // Speed up development by nuking the database when migrations change // See migrator.eraseDatabaseOnSchemaChange = true #endif migrator.registerMigration("createLine") { db in // Create a table // See try db.create(table: "Line") { t in t.autoIncrementedPrimaryKey("id") t.column("body", .text).notNull() t.column("chap", .integer).notNull() t.column("book", .text).notNull() t.column("verse", .integer) } try db.create(table: "Seg") { t in t.autoIncrementedPrimaryKey("id") t.column("seg_id", .integer).notNull() t.column("line_id", .integer).notNull() t.column("book", .text).notNull() } try db.create(table: "Ribbon") { t in t.autoIncrementedPrimaryKey("id") t.column("book", .text).notNull() t.column("scrollOffset", .integer).notNull() t.column("scrollId", .text) } try db.create(table: "SelectedRibbon") { t in t.autoIncrementedPrimaryKey("id") t.column("ribbonId", .integer).notNull() } try db.create(table: "ScrollState") { t in t.autoIncrementedPrimaryKey("id") t.column("scrollId", .text).notNull() t.column("scrollOffset", .integer).notNull() } try db.create(table: "foo2") { t in t.autoIncrementedPrimaryKey("id") t.column("ribbonId", .integer).notNull() } } // Migrations for future application versions will be inserted here: // migrator.registerMigration(...) { db in // ... // } return migrator } } // MARK: - Database Access: Writes func load(_ filename: String) -> T { let data: Data guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { fatalError("Couldn't find \(filename) in main bundle.") } do { data = try Data(contentsOf: file) } catch { fatalError("Couldn't load \(filename) from main bundle:\n\(error)") } do { let decoder = JSONDecoder() return try decoder.decode(T.self, from: data) } catch { fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") } } extension AppDatabase { func saveRibbon(_ ribbon: inout Ribbon) async throws { // if ribbon.name.isEmpty { // throw ValidationError.missingName // } ribbon = try await dbWriter.write { [ribbon] db in try ribbon.saved(db) } } func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws { // if ribbon.name.isEmpty { // throw ValidationError.missingName // } try await dbWriter.write { [selectedRibbon] db in try selectedRibbon.update(db) } } func saveScrollState(_ scrollState: inout ScrollState) async throws { // if ribbon.name.isEmpty { // throw ValidationError.missingName // } try await dbWriter.write { [scrollState] db in try scrollState.update(db) } } /// Create random Lines if the database is empty. func createRandomLinesIfEmpty() throws { let imports : JsonImport = load("john_export.json") try dbWriter.write { db in // try db.execute(sql: "DELETE FROM Line") // try db.execute(sql: "DELETE FROM Seg") // try db.execute(sql: "DELETE FROM Ribbon") // try db.execute(sql: "DELETE FROM SelectedRibbon") if try Line.all().isEmpty(db) { for l in imports.lines { print("here") print(l.body) _ = try l.inserted(db) } for l in imports.segs { print("here SEGS") print(l.seg_id) _ = try l.inserted(db) } // print("cat") // try createRandomLines(db) _ = try Ribbon(id: 1, book: "bible.john", scrollOffset: 0).inserted(db) _ = try Ribbon(id: 2, book: "bible.john", scrollOffset: 2000).inserted(db) _ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db) _ = try ScrollState(id: 1, scrollId: "1", scrollOffset: 1).inserted(db) } } } // static let uiTestLines = [ // Line(id: nil, name: "Arthur", score: 5), // Line(id: nil, name: "Barbara", score: 6), // Line(id: nil, name: "Craig", score: 8), // Line(id: nil, name: "David", score: 4), // Line(id: nil, name: "Elena", score: 1), // Line(id: nil, name: "Frederik", score: 2), // Line(id: nil, name: "Gilbert", score: 7), // Line(id: nil, name: "Henriette", score: 3)] // func createLinesForUITests() throws { // try dbWriter.write { db in // try AppDatabase.uiTestLines.forEach { Line in // _ = try Line.inserted(db) // insert but ignore inserted id // } // } // } } // MARK: - Database Access: Reads // This demo app does not provide any specific reading method, and insteadKK // gives an unrestricted read-only access to the rest of the application. // In your app, you are free to choose another path, and define focused // reading methods. extension AppDatabase { /// Provides a read-only access to the database var reader: DatabaseReader { dbWriter } }