Persist Swift types to the database with PersistableRecord and MutablePersistableRecord
The PersistableRecord and MutablePersistableRecord protocols allow Swift types to be persisted in the database. These protocols provide methods for inserting, updating, saving, and deleting records.
Use MutablePersistableRecord for struct-based records:
struct Player: MutablePersistableRecord { static let databaseTableName = "player" var id: Int64? var name: String var score: Int func encode(to container: inout PersistenceContainer) { container["id"] = id container["name"] = name container["score"] = score } // Mutating callback to capture the auto-incremented id mutating func didInsert(_ inserted: InsertionSuccess) { id = inserted.rowID }}var player = Player(id: nil, name: "Arthur", score: 100)try player.insert(db)print(player.id) // The auto-incremented id
Insert a record and fetch the complete row in one operation:
struct PartialPlayer: MutablePersistableRecord { static let databaseTableName = "player" var name: String func encode(to container: inout PersistenceContainer) { container["name"] = name }}struct FullPlayer: FetchableRecord, TableRecord { static let databaseTableName = "player" var id: Int64 var name: String var score: Int // Has a default value in the database var createdAt: Date // Generated column}var partial = PartialPlayer(name: "Arthur")let full = try partial.insertAndFetch(db, as: FullPlayer.self)print(full.score) // The default value from the databaseprint(full.createdAt) // The generated timestamp
Use insertAndFetch when your table has default values, computed columns, or triggers that modify the inserted data.
var player = try Player.fetchOne(db, id: 1)let modified = try player.updateChanges(db) { record in record.score += 10 record.name = record.name.uppercased()}
var player = try Player.fetchOne(db, id: 1)player.score = 200let updated = try player.updateAndFetch(db)print(updated.score) // Includes any database-side changes
The save(_:) method inserts or updates depending on whether the record exists:
var player = Player(id: nil, name: "Arthur", score: 100)// First call insertstry player.save(db)print(player.id) // Some id// Subsequent calls updateplayer.score = 200try player.save(db)
save(_:) determines whether to insert or update based on the primary key. If the primary key is nil or doesn’t match any row, it inserts. Otherwise, it updates.
var player = try Player.fetchOne(db, id: 1)let deleted = try player.delete(db)if deleted { print("Player was deleted")} else { print("Player was not found")}
If your type conforms to Encodable, GRDB automatically provides the encode(to:) implementation:
struct Player: Encodable { var id: Int64? var name: String var score: Int}// Just add the protocols - no encode(to:) needed!extension Player: MutablePersistableRecord { static let databaseTableName = "player" mutating func didInsert(_ inserted: InsertionSuccess) { id = inserted.rowID }}
Use MutablePersistableRecord for structs: Swift structs with value semantics work well with MutablePersistableRecord. The mutating callbacks let you capture auto-incremented IDs.
Leverage Codable: If your type conforms to Codable, you don’t need to implement encode(to:) manually. GRDB provides it automatically.
Primary keys are required for updates and deletes: The update(_:) and delete(_:) methods require a primary key to identify which row to modify. Make sure your table has a primary key defined.
Use updateChanges for efficiency: When you only want to update modified columns, use updateChanges(_:from:) or updateChanges(_:modify:) instead of update(_:).