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("pos", .integer).notNull() t.column("title", .text).notNull() 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 updateRibbonPosition(_ ribbon: inout Ribbon, _ oldPos: Int, _ newPos: Int) async throws { try await dbWriter.write { [ribbon] db in // This is only for moving back rn print("MEOW HERE") print(oldPos) print(newPos) print(ribbon) print(ribbon.id!) print("MEOW HERE 2") // try db.execute(sql: """ // BEGIN TRANSACTION; // """) do { try db.execute(sql: """ UPDATE Ribbon SET pos = CASE WHEN pos = ? THEN ? ELSE pos + 1 END WHERE (pos >= ? AND pos <= ?) """, arguments: [oldPos, newPos, newPos, oldPos]) // try db.execute(sql: """ // UPDATE Ribbon // SET pos = ? // WHERE (id = ?) // """, arguments: [newPos, ribbon.id!]) var ret = try Ribbon.fetchAll(db, sql: "SELECT * FROM Ribbon ORDER BY pos ASC") // [Player] // print(ret) print("all") print(ret) } catch { print("Error info: \(error)") } // try ribbon.saved(db) // try db.execute(sql: """ // COMMIT; // """) } } 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) } } func importJson(_ filename: String, _ db: Database) throws { let importJson : JsonImport = load(filename) if try Line.all().isEmpty(db) { for l in importJson.lines { print("importing Lines") _ = try l.inserted(db) } for l in importJson.segs { print("importing SEGS") _ = try l.inserted(db) } } } /// Create random Lines if the database is empty. func initDatabase() throws { try dbWriter.write { db in if try Line.all().isEmpty(db) { try importJson("john_export.json", db) try importJson("mark_export.json", db) _ = try Ribbon(id: 1, pos: 1, title: "John", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db) _ = try Ribbon(id: 2, pos: 2, title: "Gospel of Mark", book: "bible.mark", scrollId: "1", scrollOffset: 300).inserted(db) _ = try Ribbon(id: 3, pos: 3, title: "John 2", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db) _ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db) } } } } // 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 } }