2023-02-27 14:27:15 -08:00
|
|
|
import Foundation
|
|
|
|
import GRDB
|
|
|
|
|
|
|
|
/// AppDatabase lets the application access the database.
|
|
|
|
///
|
|
|
|
/// It applies the pratices recommended at
|
|
|
|
/// <https://github.com/groue/GRDB.swift/blob/master/Documentation/GoodPracticesForDesigningRecordTypes.md>
|
|
|
|
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 <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections>
|
|
|
|
private let dbWriter: any DatabaseWriter
|
|
|
|
|
|
|
|
/// The DatabaseMigrator that defines the database schema.
|
|
|
|
///
|
|
|
|
/// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
|
|
|
private var migrator: DatabaseMigrator {
|
|
|
|
var migrator = DatabaseMigrator()
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
// Speed up development by nuking the database when migrations change
|
|
|
|
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations>
|
|
|
|
migrator.eraseDatabaseOnSchemaChange = true
|
|
|
|
#endif
|
|
|
|
|
|
|
|
migrator.registerMigration("createLine") { db in
|
|
|
|
// Create a table
|
|
|
|
// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema>
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-03-11 20:31:48 -08:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:27:15 -08:00
|
|
|
try db.create(table: "Ribbon") { t in
|
|
|
|
t.autoIncrementedPrimaryKey("id")
|
|
|
|
t.column("book", .text).notNull()
|
|
|
|
t.column("scrollOffset", .integer).notNull()
|
|
|
|
}
|
2023-02-28 14:03:58 -08:00
|
|
|
|
|
|
|
try db.create(table: "SelectedRibbon") { t in
|
|
|
|
t.autoIncrementedPrimaryKey("id")
|
|
|
|
t.column("ribbonId", .integer).notNull()
|
|
|
|
}
|
2023-03-11 20:31:48 -08:00
|
|
|
|
|
|
|
try db.create(table: "foo2") { t in
|
|
|
|
t.autoIncrementedPrimaryKey("id")
|
|
|
|
t.column("ribbonId", .integer).notNull()
|
|
|
|
}
|
2023-02-27 14:27:15 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Migrations for future application versions will be inserted here:
|
|
|
|
// migrator.registerMigration(...) { db in
|
|
|
|
// ...
|
|
|
|
// }
|
|
|
|
|
|
|
|
return migrator
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Database Access: Writes
|
|
|
|
|
|
|
|
|
|
|
|
func load<T: Decodable>(_ 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 {
|
|
|
|
|
2023-02-28 14:03:58 -08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 10:47:34 -08:00
|
|
|
|
2023-02-28 14:03:58 -08:00
|
|
|
func saveSelectedRibbon(_ selectedRibbon: inout SelectedRibbon) async throws {
|
2023-03-01 10:47:34 -08:00
|
|
|
// if ribbon.name.isEmpty {
|
2023-02-28 14:03:58 -08:00
|
|
|
// throw ValidationError.missingName
|
|
|
|
// }
|
2023-03-01 10:47:34 -08:00
|
|
|
try await dbWriter.write { [selectedRibbon] db in
|
|
|
|
try selectedRibbon.update(db)
|
2023-02-28 14:03:58 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 10:47:34 -08:00
|
|
|
|
2023-02-27 14:27:15 -08:00
|
|
|
/// Create random Lines if the database is empty.
|
|
|
|
func createRandomLinesIfEmpty() throws {
|
|
|
|
|
|
|
|
let imports : JsonImport = load("john_export.json")
|
|
|
|
|
2023-03-11 20:31:48 -08:00
|
|
|
|
2023-02-27 14:27:15 -08:00
|
|
|
try dbWriter.write { db in
|
2023-03-11 20:31:48 -08:00
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
2023-02-27 14:27:15 -08:00
|
|
|
if try Line.all().isEmpty(db) {
|
|
|
|
for l in imports.lines {
|
|
|
|
print("here")
|
|
|
|
print(l.body)
|
|
|
|
_ = try l.inserted(db)
|
|
|
|
}
|
2023-03-11 20:31:48 -08:00
|
|
|
|
|
|
|
for l in imports.segs {
|
|
|
|
print("here SEGS")
|
|
|
|
print(l.seg_id)
|
|
|
|
_ = try l.inserted(db)
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:27:15 -08:00
|
|
|
// print("cat")
|
|
|
|
// try createRandomLines(db)
|
2023-03-11 20:31:48 -08:00
|
|
|
|
|
|
|
_ = try Ribbon(id: 1, book: "bible.john", scrollOffset: 0).inserted(db)
|
|
|
|
_ = try Ribbon(id: 2, book: "bible.john", scrollOffset: 2000).inserted(db)
|
2023-02-28 14:03:58 -08:00
|
|
|
_ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db)
|
2023-02-27 14:27:15 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2023-02-28 14:03:58 -08:00
|
|
|
// This demo app does not provide any specific reading method, and insteadKK
|
2023-02-27 14:27:15 -08:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|