This guide helps you upgrade your applications from GRDB 6 to GRDB 7.
- Preparing the Migration to GRDB 7
- Recommendations Regarding Swift Concurrency
- New requirements
- Companion Libraries
- Column Coding Strategies
- Cancellable Async Database Accesses
- Default Transaction Kind
- ValueObservation and the Main Actor
- Access to SQLite C functions
- The Record Base Class is Discouraged
- Other Changes
Before upgrading, ensure you are using the latest GRDB 6 release and address any deprecation warnings. Once this is done, proceed with upgrading to GRDB 7. Due to breaking changes, your application may no longer compile. Follow the fix-it suggestions for simple syntax updates, and review the specific modifications described below.
GRDB 7 requires Xcode 16+ and a Swift 6 compiler.
Depending of the language mode and level of concurrency checkings used by your application, you may see warnings or errors. See Migrating to Swift 6 on swift.org for more information about Swift 6. See Swift Concurrency and GRDB for GRDB-focused advice.
GRDB requirements have been bumped:
- Swift Compiler 6+ (was Swift 5.7+).
- Xcode 16.0+ (was Xcode 14.0+)
- iOS 13+ (was iOS 11+)
- macOS 10.15+ (was macOS 10.13+)
- tvOS 13+ (was tvOS 11+)
- watchOS 7.0+ (was watchOS 4+)
- SQLite 3.20.0+ (was SQLite 3.19.3+)
Companion libraries have been updated for GRDB 7:
- GRDBQuery releases (SwiftUI)
- GRDBSnapshotTesting releases (Snapshot Testing)
In GRDB 6, Codable record types can specify how Data
, Date
, and UUID
properties are stored in the database:
// GRDB 6
struct Player {
static let databaseDataDecodingStrategy = ...
static let databaseDateDecodingStrategy = ...
static let databaseDataEncodingStrategy = ...
static let databaseDateEncodingStrategy = ...
static let databaseUUIDEncodingStrategy = ...
}
These properties have been removed in GRDB 7. You must now define methods that accept a column argument:
// GRDB 7
struct Player {
static func databaseDataDecodingStrategy(for column: String) -> DatabaseDataDecodingStrategy { ... }
static func databaseDateDecodingStrategy(for column: String) -> DatabaseDateDecodingStrategy { ...}
static func databaseDataEncodingStrategy(for column: String) -> DatabaseDataEncodingStrategy { ... }
static func databaseDateEncodingStrategy(for column: String) -> DatabaseDateEncodingStrategy { ... }
static func databaseUUIDEncodingStrategy(for column: String) -> DatabaseUUIDEncodingStrategy { ... }
}
In GRDB 6, asynchronous database accesses such as try await read { ... }
or try await write { ... }
complete even if the wrapper Task is cancelled.
In GRDB 7, asynchronous database accesses respect Task cancellation. If a Task is cancelled, reads and writes throw a CancellationError
, pending transactions are rolled back, and the database is not modified. The only SQL statement that can execute in a cancelled database access is ROLLBACK
.
The effect of this change on your application depends on how it uses tasks. For example, take care of database jobs initiated frop the task
SwiftUI modifier.
If you want an asynchronous database access to always complete, regardless of Task cancellation, wrap it in an unstructured Task:
// Create a new Task in order to ignore
// cancellation of the current task, and
// make sure database changes are always
// committed to disk.
let task = Task {
try await writer.write { ... }
}
// If needed, wait for the database job to complete:
try await task.value
Other asynchronous database accesses, such as methods accepting a completion blocks (asyncRead
, etc.), Combine publishers, RxSwift observables, do not handle cancellation and will proceed to completion by default.
Some applications specify a default transaction kind, which was previously recommended in the Sharing a Database guide:
// GRDB 6
var config = Configuration()
config.defaultTransactionKind = .immediate
In GRDB 7, Configuration
no longer has a defaultTransactionKind
property, because transactions are automatically managed. Reads use DEFERRED transactions, and writes use IMMEDIATE transactions.
You can still specify a transaction kind explicitly when necessary. See Transaction Kinds for details.
In GRDB 7, ValueObservation
fosters the main actor, because it is frequently used to automatically update database values on screen.
By default, its start
method must be started on the main actor, and its notification callbacks run on the main actor as well.
It is not necessary to call MainActor.assumeIsolated
in ValueObservation
callbacks:
// GRDB 7
@MainActor func startObservation() {
let observation = ValueObservation.tracking { ... }
let cancellable = observation.start(in: dbQueue) { error in
// This closure is MainActor-isolated.
} onChange: { value in
// This closure is MainActor-isolated.
print("Fresh value", value)
}
}
You can opt-out of the main actor with a specific scheduler such as .async(onQueue:)
:
func startObservation() {
let observation = ValueObservation.tracking { ... }
let cancellable = observation.start(
in: dbQueue,
scheduling: .async(onQueue: .main))
{ error in
// Called on the specified dispatch queue.
} onChange: { value in
// Called on the specified dispatch queue.
print("Fresh value", value)
}
}
In GRDB 6, the underlying C SQLite library is implicitly available:
// GRDB 6
import GRDB
let sqliteVersion = sqlite3_libversion_number()
In GRDB 7, you may need an additional import, depending on how GRDB is integrated:
-
If your app uses the GRDB Swift Package Manager (SPM) package:
import SQLite3 let sqliteVersion = sqlite3_libversion_number()
The GRDB 6 SPM package included a product named "CSQLite." In GRDB 7, this product has been renamed "GRDBSQLite." Update your dependencies accordingly. It is unclear at the time of writing whether some projects can remove this dependency.
-
If your app uses SQLCipher:
import SQLCipher let sqliteVersion = sqlite3_libversion_number()
-
In other cases, no additional import is needed.
Subclassing the Record class is discouraged in GRDB 7. Present in GRDB 1.0, in 2017, it has served its purpose.
It is not recommended to define any new type that subclass Record
.
It is recommended to refactor Record
subclasses into Swift structs, before you enable the strict concurrency checkings or the Swift 6 language mode. See Migrating to Swift 6 for more information about Swift 6 language modes.
For example:
// GRDB 6
class Player: Record {
var id: UUID
var name: String
var score: Int
override class var databaseTableName: String { "player" }
init(id: UUID, name: String, score: Int) { ... }
required init(row: Row) throws { ... }
override func encode(to container: inout PersistenceContainer) throws { ...}
}
// GRDB 7
struct Player: Codable {
var id: UUID
var name: String
var score: Int
}
extension Player: FetchableRecord, PersistableRecord { }
Do not miss Swift Concurrency and GRDB, for more recommendations regarding non-Sendable record types in GRDB.
-
DatabasePool.concurrentRead
has been removed. UseasyncConcurrentRead
instead. -
The
PersistenceContainer
subscript no longer guarantees that the value returned is the same as what was previously set. It only guarantees that both values are encoded identically in the database. -
The async sequence returned by
ValueObservation.values
now iterates on the cooperative thread pool by default. Use .mainActor as the scheduler if you need the previous behavior.