Hello, I am evaluating GRDB for use in my app and wanted to get some input on whether something I'm trying is appropriate, or if it's abusing GRDB, or otherwise terrible for some reason. Thanks in advance for any advice!
For some of the data in my app, I want to use GRDB's records traditionally: set up specific tables for them, properly migrate them between app versions, etc. But for much of my data, I would like to use GRDB as a key-value store of codable structs. It would be used for some long-term ish caching purposes, but can be cleared at any time and if a record format changes between app releases, it's fine because it can just re-fetched. For quick illustration purposes, the hierarchy I'm setting up can be represented with this swift dictionary:
var store = [String: [String: (value: Any, metadata: Any?)]]
Top level are "collections", and the inner level are "keys". A key can have a value and optional metadata associated with it. Continuing with the dictionary version, looking up an Article
with id "1"
would look like:
let article = store["Article"]?["1"]?.value as? Article
My app would be storing in the low 1000s of entries.
GRDB
Here's a slightly modified version of how I'm accomplishing this with GRDB.
First I have two protocols:
public protocol Persistable: Codable {
static var collection: String { get }
var id: String { get }
}
public protocol PersistableWithMetadata: Persistable {
associatedtype Metadata: Codable
}
These are what my app's records would deal with. Everything below would be "private" to a wrapper framework. Various abstractions for reading Persistable
s and PersistableWithMetadata
s would be built, so I could write code like Article.value(for: "1", in: database)
I then create two record wrapper types:
struct DatabaseValueWrapper<Wrapped: Persistable>: Codable, FetchableRecord, PersistableRecord {
static var databaseTableName: String {
"values"
}
enum Columns {
static var id: Column { Column(CodingKeys.id) }
static var value: Column { Column(CodingKeys.value) }
}
var id: String
var value: Wrapped
init(_ wrapped: Wrapped) {
self.id = "\(Wrapped.collection).\(wrapped.id)"
self.value = wrapped
}
}
struct DatabaseMetadataWrapper<Wrapped: PersistableWithMetadata >: Codable, FetchableRecord, PersistableRecord {
static var databaseTableName: String {
"metadatas"
}
enum Columns {
static var id: Column { Column(CodingKeys.id) }
static var metadata: Column { Column(CodingKeys.metadata) }
}
var id: String
var metadata: Wrapped.Metadata
init(id: String, _ wrapped: Wrapped.Metadata) {
self.id = "\(Wrapped.collection).\(id)"
self.metadata = wrapped
}
}
I then set up my tables like this:
registerMigration("createWrapperTables") { d in
try d.create(table: "values") { table in
table.column("id", .text).indexed().primaryKey(onConflict: .replace)
table.column("value", .blob)
}
try d.create(table: "metadatas") { table in
table.column("id", .text).indexed().primaryKey(onConflict: .replace)
table.column("metadata", .blob)
}
}