What it is
GRDB is a SQLite toolkit for Swift offering raw SQL, a query interface, database observation and record types, with strong concurrency guarantees.
Access the database through a queue or pool. Records map rows to types, and ValueObservation emits whenever the underlying data changes.
Installation
.package(url: "https://github.com/groue/GRDB.swift.git", from: "7.0.0")Getting started
The smallest useful thing you can do with it, and what each part means.
swift
struct Book: Codable, FetchableRecord, MutablePersistableRecord {
var id: Int64?
var title: String
var year: Int
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
}
var migrator = DatabaseMigrator()
migrator.registerMigration("createBook") { db in
try db.create(table: "book") { t in
t.autoIncrementedPrimaryKey("id")
t.column("title", .text).notNull()
t.column("year", .integer).notNull().indexed()
}
}
try migrator.migrate(dbQueue)
let recent = try await dbQueue.read { db in
try Book.filter(Column("year") >= 1990)
.order(Column("year").desc)
.limit(10)
.fetchAll(db)
}Advanced usage
Where the library earns its place over a simpler alternative.
swift
// Emits the current value, then again on every relevant change.
let observation = ValueObservation.tracking { db in
try Book.order(Column("year").desc).fetchAll(db)
}
for try await books in observation.values(in: dbQueue) {
self.books = books
}
// A pool allows concurrent reads alongside one writer (WAL mode).
var config = Configuration()
config.prepareDatabase { db in
try db.execute(sql: "PRAGMA journal_mode = WAL")
}
let dbPool = try DatabasePool(path: path, configuration: config)Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- SQLite error 5: database is locked
- A long write is blocking. Shorten write transactions, and use DatabasePool with WAL so readers do not contend with the writer.
- Records do not save the generated id
- Implement didInsert on a MutablePersistableRecord to capture the assigned row id.
Best practices
- Use DatabasePool with WAL for concurrent reads; DatabaseQueue serialises everything.
- Always use DatabaseMigrator rather than ad hoc schema changes.
- Use ValueObservation for UI rather than polling.
- Keep write blocks short — they hold the write lock for their duration.
Background
Why it exists, and what it was reacting to.
GRDB's distinguishing feature is its concurrency model: a DatabaseQueue or DatabasePool enforces safe access patterns, so the data races that plague hand-rolled SQLite code are prevented by the API.
