add iOS
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
/// A type that can be persisted in the database, and mutates on insertion.
|
||||
///
|
||||
/// ## Overview
|
||||
///
|
||||
/// A `MutablePersistableRecord` instance mutates on insertion. This protocol
|
||||
/// is suited for record types that are a `struct`, and target a database table
|
||||
/// where ids are generated on insertion. Such records implement the
|
||||
/// ``didInsert(_:)-109jm`` callback in order to grab this id. For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // CREATE TABLE player (
|
||||
/// // id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/// // name TEXT NOT NULL,
|
||||
/// // score INTEGER NOT NULL
|
||||
/// // )
|
||||
/// struct Player: Encodable {
|
||||
/// var id: Int64?
|
||||
/// var name: String
|
||||
/// var score: Int
|
||||
/// }
|
||||
///
|
||||
/// extension Player: MutablePersistableRecord {
|
||||
/// mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
/// id = inserted.rowID
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// try dbQueue.write { db in
|
||||
/// var player = Player(id: nil, name:: "Arthur", score: 1000)
|
||||
/// try player.insert(db)
|
||||
/// print(player.id) // Some id that is not nil
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Other record types (classes, and generally records that do not mutate on
|
||||
/// insertion) should prefer the ``PersistableRecord`` protocol instead.
|
||||
///
|
||||
/// ## Conforming to the MutablePersistableRecord Protocol
|
||||
///
|
||||
/// To conform to `MutablePersistableRecord`, provide an implementation for the
|
||||
/// ``EncodableRecord/encode(to:)-k9pf`` method. This implementation is
|
||||
/// ready-made for `Encodable` types.
|
||||
///
|
||||
/// You configure the database table where records are persisted with the
|
||||
/// ``TableRecord`` inherited protocol.
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Testing if a Record Exists in the Database
|
||||
///
|
||||
/// - ``exists(_:)``
|
||||
///
|
||||
/// ### Inserting a Record
|
||||
///
|
||||
/// - ``insert(_:onConflict:)``
|
||||
/// - ``inserted(_:onConflict:)``
|
||||
/// - ``upsert(_:)``
|
||||
///
|
||||
/// ### Inserting a Record and Fetching the Inserted Row
|
||||
///
|
||||
/// - ``insertAndFetch(_:onConflict:)``
|
||||
/// - ``insertAndFetch(_:onConflict:as:)``
|
||||
/// - ``insertAndFetch(_:onConflict:selection:fetch:)``
|
||||
/// - ``upsertAndFetch(_:onConflict:doUpdate:)``
|
||||
/// - ``upsertAndFetch(_:as:onConflict:doUpdate:)``
|
||||
///
|
||||
/// ### Updating a Record
|
||||
///
|
||||
/// See inherited ``TableRecord`` methods for batch updates.
|
||||
///
|
||||
/// - ``update(_:onConflict:)``
|
||||
/// - ``update(_:onConflict:columns:)-4foo1``
|
||||
/// - ``update(_:onConflict:columns:)-5hxyx``
|
||||
/// - ``updateChanges(_:onConflict:from:)``
|
||||
/// - ``updateChanges(_:onConflict:modify:)``
|
||||
///
|
||||
/// ### Updating a Record and Fetching the Updated Row
|
||||
///
|
||||
/// - ``updateAndFetch(_:onConflict:)``
|
||||
/// - ``updateAndFetch(_:onConflict:as:)``
|
||||
/// - ``updateAndFetch(_:onConflict:columns:selection:fetch:)-7s7y1``
|
||||
/// - ``updateAndFetch(_:onConflict:columns:selection:fetch:)-30d2v``
|
||||
/// - ``updateAndFetch(_:onConflict:selection:fetch:)``
|
||||
/// - ``updateChangesAndFetch(_:onConflict:modify:)``
|
||||
/// - ``updateChangesAndFetch(_:onConflict:as:modify:)``
|
||||
/// - ``updateChangesAndFetch(_:onConflict:selection:fetch:modify:)``
|
||||
///
|
||||
/// ### Saving a Record
|
||||
///
|
||||
/// - ``save(_:onConflict:)``
|
||||
/// - ``saved(_:onConflict:)``
|
||||
///
|
||||
/// ### Saving a Record and Fetching the Saved Row
|
||||
///
|
||||
/// - ``saveAndFetch(_:onConflict:)``
|
||||
/// - ``saveAndFetch(_:onConflict:as:)``
|
||||
/// - ``saveAndFetch(_:onConflict:selection:fetch:)``
|
||||
///
|
||||
/// ### Deleting a Record
|
||||
///
|
||||
/// See inherited ``TableRecord`` methods for batch deletes.
|
||||
///
|
||||
/// - ``delete(_:)``
|
||||
///
|
||||
/// ### Persistence Callbacks
|
||||
///
|
||||
/// - ``willDelete(_:)-7rmqk``
|
||||
/// - ``willInsert(_:)-1xfwo``
|
||||
/// - ``willSave(_:)-6jitc``
|
||||
/// - ``willUpdate(_:columns:)-3oko4``
|
||||
/// - ``didDelete(deleted:)-7sq9c``
|
||||
/// - ``didInsert(_:)-109jm``
|
||||
/// - ``didSave(_:)-177yz``
|
||||
/// - ``didUpdate(_:)-1oql8``
|
||||
/// - ``aroundDelete(_:delete:)-8w9ei``
|
||||
/// - ``aroundInsert(_:insert:)-67r8o``
|
||||
/// - ``aroundSave(_:save:)-5o9jz``
|
||||
/// - ``aroundUpdate(_:columns:update:)-ka41``
|
||||
/// - ``InsertionSuccess``
|
||||
/// - ``PersistenceSuccess``
|
||||
///
|
||||
/// ### Configuring Persistence
|
||||
///
|
||||
/// - ``persistenceConflictPolicy-1isyv``
|
||||
/// - ``PersistenceConflictPolicy``
|
||||
public protocol MutablePersistableRecord: EncodableRecord, TableRecord {
|
||||
/// The policy that handles SQLite conflicts when records are inserted
|
||||
/// or updated.
|
||||
///
|
||||
/// The default implementation uses the ABORT policy for both insertions and
|
||||
/// updates, and has GRDB generate regular INSERT and UPDATE queries.
|
||||
///
|
||||
/// See <https://www.sqlite.org/lang_conflict.html>
|
||||
static var persistenceConflictPolicy: PersistenceConflictPolicy { get }
|
||||
|
||||
// MARK: Insertion Callbacks
|
||||
|
||||
/// Persistence callback called before the record is inserted.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
mutating func willInsert(_ db: Database) throws
|
||||
|
||||
/// Persistence callback called around the record insertion.
|
||||
///
|
||||
/// If you provide a custom implementation of this method, you must call
|
||||
/// the `insert` parameter at some point in your implementation, and you
|
||||
/// must rethrow its eventual error.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: MutablePersistableRecord {
|
||||
/// func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
|
||||
/// print("Player will insert")
|
||||
/// _ = try insert()
|
||||
/// print("Player did insert")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
/// - parameter insert: A function that inserts the record, and returns
|
||||
/// information about the inserted row.
|
||||
func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws
|
||||
|
||||
/// Persistence callback called upon successful insertion.
|
||||
///
|
||||
/// The default implementation does nothing.
|
||||
///
|
||||
/// You can provide a custom implementation in order to grab the
|
||||
/// auto-incremented id:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: MutablePersistableRecord {
|
||||
/// var id: Int64?
|
||||
/// var name: String
|
||||
///
|
||||
/// mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
/// id = inserted.rowID
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - parameter inserted: Information about the inserted row.
|
||||
mutating func didInsert(_ inserted: InsertionSuccess)
|
||||
|
||||
// MARK: Update Callbacks
|
||||
|
||||
/// Persistence callback called before the record is updated.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
func willUpdate(_ db: Database, columns: Set<String>) throws
|
||||
|
||||
/// Persistence callback called around the record update.
|
||||
///
|
||||
/// If you provide a custom implementation of this method, you must call
|
||||
/// the `update` parameter at some point in your implementation, and you
|
||||
/// must rethrow its eventual error.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: MutablePersistableRecord {
|
||||
/// func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
|
||||
/// print("Player will update")
|
||||
/// _ = try update()
|
||||
/// print("Player did update")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
/// - parameter columns: The updated columns.
|
||||
/// - parameter update: A function that updates the record. Its result is
|
||||
/// reserved for GRDB usage.
|
||||
func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws
|
||||
|
||||
/// Persistence callback called upon successful update.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter updated: Reserved for GRDB usage.
|
||||
func didUpdate(_ updated: PersistenceSuccess)
|
||||
|
||||
// MARK: Save Callbacks
|
||||
|
||||
/// Persistence callback called before the record is updated or inserted.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
func willSave(_ db: Database) throws
|
||||
|
||||
/// Persistence callback called around the record update or insertion.
|
||||
///
|
||||
/// If you provide a custom implementation of this method, you must call
|
||||
/// the `save` parameter at some point in your implementation, and you
|
||||
/// must rethrow its eventual error.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: MutablePersistableRecord {
|
||||
/// func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
|
||||
/// print("Player will save")
|
||||
/// _ = try save()
|
||||
/// print("Player did save")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
/// - parameter update: A function that updates the record. Its result is
|
||||
/// reserved for GRDB usage.
|
||||
func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws
|
||||
|
||||
/// Persistence callback called upon successful update or insertion.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter saved: Reserved for GRDB usage.
|
||||
func didSave(_ saved: PersistenceSuccess)
|
||||
|
||||
// MARK: Deletion Callbacks
|
||||
|
||||
/// Persistence callback called before the record is deleted.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
func willDelete(_ db: Database) throws
|
||||
|
||||
/// Persistence callback called around the destruction of the record.
|
||||
///
|
||||
/// If you provide a custom implementation of this method, you must call
|
||||
/// the `delete` parameter at some point in your implementation, and you
|
||||
/// must rethrow its eventual error.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: MutablePersistableRecord {
|
||||
/// func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
|
||||
/// print("Player will delete")
|
||||
/// _ = try delete()
|
||||
/// print("Player did delete")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
/// - parameter delete: A function that deletes the record and returns
|
||||
/// whether a row was deleted in the database.
|
||||
func aroundDelete(_ db: Database, delete: () throws -> Bool) throws
|
||||
|
||||
/// Persistence callback called upon successful deletion.
|
||||
///
|
||||
/// Default implementation does nothing.
|
||||
///
|
||||
/// - parameter deleted: Whether a row was deleted in the database.
|
||||
func didDelete(deleted: Bool)
|
||||
}
|
||||
|
||||
extension MutablePersistableRecord {
|
||||
public static var persistenceConflictPolicy: PersistenceConflictPolicy {
|
||||
PersistenceConflictPolicy(insert: .abort, update: .abort)
|
||||
}
|
||||
|
||||
/// Call for programmer errors from the `aroundXxx` callbacks.
|
||||
@usableFromInline
|
||||
func persistenceCallbackMisuse(_ callbackName: String) throws -> Never {
|
||||
let message = """
|
||||
Incorrect implementation of the `\(Self.self).\(callbackName)` persistence callback: \
|
||||
the action function was not called, or its error was not rethrown.
|
||||
"""
|
||||
// This is a programmer error, but we must not crash, because it can
|
||||
// only be detected in case of database errors, which happen
|
||||
// infrequently. That's why we gently throw SQLITE_MISUSE.
|
||||
throw DatabaseError(
|
||||
resultCode: .SQLITE_MISUSE,
|
||||
message: message)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Existence Check
|
||||
|
||||
extension MutablePersistableRecord {
|
||||
/// Returns whether the primary key of the record matches a row in
|
||||
/// the database.
|
||||
///
|
||||
/// - parameter db: A database connection.
|
||||
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
|
||||
public func exists(_ db: Database) throws -> Bool {
|
||||
guard let statement = try DAO(db, self).existsStatement() else {
|
||||
// Nil primary key
|
||||
return false
|
||||
}
|
||||
return try Bool.fetchOne(statement)!
|
||||
}
|
||||
}
|
||||
|
||||
/// The `MutablePersistableRecord` protocol uses this type in order to handle
|
||||
/// SQLite conflicts when records are inserted or updated.
|
||||
///
|
||||
/// See `MutablePersistableRecord.persistenceConflictPolicy`.
|
||||
///
|
||||
/// See <https://www.sqlite.org/lang_conflict.html>
|
||||
public struct PersistenceConflictPolicy: Sendable {
|
||||
/// The conflict resolution algorithm for insertions
|
||||
public let conflictResolutionForInsert: Database.ConflictResolution
|
||||
|
||||
/// The conflict resolution algorithm for updates
|
||||
public let conflictResolutionForUpdate: Database.ConflictResolution
|
||||
|
||||
/// Creates a policy
|
||||
public init(insert: Database.ConflictResolution = .abort, update: Database.ConflictResolution = .abort) {
|
||||
self.conflictResolutionForInsert = insert
|
||||
self.conflictResolutionForUpdate = update
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a successful record insertion.
|
||||
///
|
||||
/// `InsertionSuccess` gives the auto-incremented id after a successful
|
||||
/// record insertion. For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: Encodable, MutablePersistableRecord {
|
||||
/// var id: Int64?
|
||||
/// var name: String
|
||||
///
|
||||
/// mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
/// id = inserted.rowID
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// try dbQueue.write { db in
|
||||
/// var player = Player(id: nil, name: "Alice")
|
||||
/// try player.insert(db)
|
||||
/// print(player.id) // The inserted id
|
||||
/// }
|
||||
/// ```
|
||||
public struct InsertionSuccess {
|
||||
/// The rowid of the inserted record.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// struct Player: Encodable, MutablePersistableRecord {
|
||||
/// var id: Int64?
|
||||
/// var name: String
|
||||
///
|
||||
/// mutating func didInsert(_ inserted: InsertionSuccess) {
|
||||
/// id = inserted.rowID
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To learn about rowids, see <https://www.sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key>.
|
||||
public var rowID: Int64
|
||||
|
||||
/// The name of the eventual INTEGER PRIMARY KEY column.
|
||||
public var rowIDColumn: String?
|
||||
|
||||
// Used by the Record class in order to manage its `hasDatabaseChanges` flag.
|
||||
/// The persistence container that was inserted.
|
||||
///
|
||||
/// If the database table has a rowid column, the persistence container
|
||||
/// contains the rowid of the inserted record.
|
||||
public var persistenceContainer: PersistenceContainer
|
||||
}
|
||||
|
||||
/// The result of a successful record persistence (insert or update).
|
||||
public struct PersistenceSuccess {
|
||||
// Used by the Record class in order to manage its `hasDatabaseChanges` flag.
|
||||
/// The persistence container that was saved.
|
||||
///
|
||||
/// After an insert, and if the database table has a rowid column, the
|
||||
/// persistence container contains the rowid of the inserted record.
|
||||
public var persistenceContainer: PersistenceContainer
|
||||
|
||||
init(persistenceContainer: PersistenceContainer) {
|
||||
self.persistenceContainer = persistenceContainer
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
init(_ inserted: InsertionSuccess) {
|
||||
self.init(persistenceContainer: inserted.persistenceContainer)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user