MutablePersistableRecord

Types that adopt MutablePersistableRecord can be inserted, updated, and deleted.

  • persistenceConflictPolicy Default implementation

    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

    Default Implementation

Insertion Callbacks

  • willInsert(_:) Default implementation

    Persistence callback called before the record is inserted.

    Default implementation does nothing.

    Default Implementation

  • aroundInsert(_:insert:) Default implementation

    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:

    struct Player: MutablePersistableRecord {
        func aroundInsert(_ db: Database, insert: () throws -> InsertionSuccess) throws {
            print("Player will insert")
            _ = try insert()
            print("Player did insert")
        }
    }
    

    Default Implementation

  • didInsert(_:) Default implementation

    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:

    struct Player: MutablePersistableRecord {
        var id: Int64?
        var name: String
    
        mutating func didInsert(_ inserted: InsertionSuccess) {
            id = inserted.rowID
        }
    }
    

    Default Implementation

Update Callbacks

  • willUpdate(_:columns:) Default implementation

    Persistence callback called before the record is updated.

    Default implementation does nothing.

    Default Implementation

  • aroundUpdate(_:columns:update:) Default implementation

    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:

    struct Player: MutablePersistableRecord {
        func aroundUpdate(_ db: Database, columns: Set<String>, update: () throws -> PersistenceSuccess) throws {
            print("Player will update")
            _ = try update()
            print("Player did update")
        }
    }
    

    Default Implementation

  • didUpdate(_:) Default implementation

    Persistence callback called upon successful update.

    Default implementation does nothing.

    Default Implementation

Save Callbacks

  • willSave(_:) Default implementation

    Persistence callback called before the record is updated or inserted.

    Default implementation does nothing.

    Default Implementation

  • aroundSave(_:save:) Default implementation

    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:

    struct Player: MutablePersistableRecord {
        func aroundSave(_ db: Database, save: () throws -> PersistenceSuccess) throws {
            print("Player will save")
            _ = try save()
            print("Player did save")
        }
    }
    

    Default Implementation

  • didSave(_:) Default implementation

    Persistence callback called upon successful update or insertion.

    Default implementation does nothing.

    Default Implementation

Deletion Callbacks

  • willDelete(_:) Default implementation

    Persistence callback called before the record is deleted.

    Default implementation does nothing.

    Default Implementation

  • aroundDelete(_:delete:) Default implementation

    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:

    struct Player: MutablePersistableRecord {
        func aroundDelete(_ db: Database, delete: () throws -> Bool) throws {
            print("Player will delete")
            _ = try delete()
            print("Player did delete")
        }
    }
    

    Default Implementation

  • didDelete(deleted:) Default implementation

    Persistence callback called upon successful deletion.

    Default implementation does nothing.

    Default Implementation

Delete

  • delete(_:) Extension method

    Executes a DELETE statement.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs.

Insert

  • insert(_:onConflict:) Extension method

    Executes an INSERT statement.

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • inserted(_:onConflict:) Extension method

    Executes an INSERT statement, and returns the inserted record.

    Usage:

    let player = Player(id: nil, name: "Arthur")
    let insertedPlayer = try dbQueue.write { db in
        try player.inserted(db)
    }
    print(player.id)         // nil
    print(insertedPlayer.id) // some id
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

Insert and Fetch

  • Executes an INSERT ... RETURNING ... statement, and returns the inserted record.

    This method helps dealing with default column values and generated columns.

    For example:

    let player: Player = ...
    let insertedPlayer = player.insertAndFetch(db)
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an INSERT ... RETURNING ... statement, and returns a new record built from the inserted row.

    This method helps dealing with default column values and generated columns.

    For example:

    // A table with an auto-incremented primary key and a default value
    try dbQueue.write { db in
        try db.execute(sql: """
            CREATE TABLE player(
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              name TEXT,
              score INTEGER DEFAULT 1000)
            """)
    }
    
    // A player with partial database information
    struct PartialPlayer: MutablePersistableRecord {
        static let databaseTableName = "player"
        var name: String
    }
    
    // A full player, with all database information
    struct Player: TableRecord, FetchableRecord {
        var id: Int64
        var name: String
        var score: Int
    }
    
    // Insert a base player, get a full one
    try dbQueue.write { db in
        var partialPlayer = PartialPlayer(name: "Alice")
    
        // INSERT INTO player (name) VALUES ('Alice') RETURNING *
        if let player = try partialPlayer.insertAndFetch(db, as: FullPlayer.self) {
            print(player.id)    // The inserted id
            print(player.name)  // The inserted name
            print(player.score) // The default score
        }
    }
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an INSERT ... RETURNING ... statement, and returns the selected columns from the inserted row.

    This method helps dealing with default column values and generated columns.

    For example:

    // A table with an auto-incremented primary key and a default value
    try dbQueue.write { db in
        try db.execute(sql: """
            CREATE TABLE player(
              id INTEGER PRIMARY KEY AUTOINCREMENT,
              name TEXT,
              score INTEGER DEFAULT 1000)
            """)
    }
    
    // A player with partial database information
    struct PartialPlayer: MutablePersistableRecord {
        static let databaseTableName = "player"
        var name: String
    }
    
    // Insert a base player, get the inserted score
    try dbQueue.write { db in
        var partialPlayer = PartialPlayer(name: "Alice")
    
        // INSERT INTO player (name) VALUES ('Alice') RETURNING score
        let score = try partialPlayer.insertAndFetch(db, selection: [Column("score")]) { statement in
            try Int.fetchOne(statement)
        }
        print(score) // The inserted score
    }
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

    Precondition

    selection is not empty.

Save

  • save(_:onConflict:) Extension method

    Executes an INSERT or UPDATE statement.

    If the receiver has a non-nil primary key and a matching row in the database, this method performs an update.

    Otherwise, performs an insert.

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • saved(_:onConflict:) Extension method

    Executes an INSERT or UPDATE statement, and returns the saved record.

    Usage:

    let player = Player(id: nil, name: "Arthur")
    let savedPlayer = try dbQueue.write { db in
        try player.saved(db)
    }
    print(player.id)      // nil
    print(savedPlayer.id) // some id
    

    Throws

    A DatabaseError whenever an SQLite error occurs.

Save and Fetch

  • saveAndFetch(_:onConflict:) Extension method

    Executes an INSERT ... RETURNING ... or UPDATE ... RETURNING ... statement, and returns the saved row.

    If the receiver has a non-nil primary key and a matching row in the database, this method performs an update. Otherwise, it performs an insert.

    This method helps dealing with default column values and generated columns.

    For example:

    let player: Player = ...
    let savedPlayer = player.saveAndFetch(db)
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an INSERT ... RETURNING ... or UPDATE ... RETURNING ... statement, and returns a new record built from the saved row.

    If the receiver has a non-nil primary key and a matching row in the database, this method performs an update. Otherwise, it performs an insert.

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an INSERT ... RETURNING ... or UPDATE ... RETURNING ... statement, and returns the selected columns from the saved row.

    If the receiver has a non-nil primary key and a matching row in the database, this method performs an update. Otherwise, it performs an insert.

    Throws

    A DatabaseError whenever an SQLite error occurs.

    Precondition

    selection is not empty.

Update

  • Executes an UPDATE statement on the provided columns.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database.
  • update(_:onConflict:) Extension method

    Executes an UPDATE statement on all columns.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database.
  • If the record has any difference from the other record, executes an UPDATE statement so that those differences and only those differences are saved in the database.

    For example:

    if let oldPlayer = try Player.fetchOne(db, key: 42) {
        var newPlayer = oldPlayer
        newPlayer.score += 10
        newPlayer.hasAward = true
        try newPlayer.updateChanges(db, from: oldRecord)
    }
    

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.

    See also

    updateChanges(_:with:)
  • Modifies the record according to the provided modify closure, and executes an UPDATE statement that updates the modified columns, if and only the record was modified.

    For example:

    if var player = try Player.fetchOne(db, key: 42) {
        try player.updateChanges(db) {
            $0.score += 10
            $0.hasAward = true
        }
    }
    

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.

Update and Fetch

  • Executes an UPDATE ... RETURNING ... statement, and returns the updated record.

    This method helps dealing with default column values and generated columns.

    For example:

    let player: Player = ...
    let updatedPlayer = player.updateAndFetch(db)
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an UPDATE ... RETURNING ... statement on all columns, and returns a new record built from the updated row.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database.
  • Modifies the record according to the provided modify closure, and executes an UPDATE ... RETURNING ... statement that updates the modified columns, if and only the record was modified. The method returns a new record built from the updated row.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.
  • Modifies the record according to the provided modify closure, and executes an UPDATE ... RETURNING ... statement that updates the modified columns, if and only the record was modified. The method returns a new record built from the updated row.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.
  • Executes an UPDATE ... RETURNING ... statement on the provided columns, and returns the selected columns from the updated row.

    For example:

    try dbQueue.write { db in
        // UPDATE player SET score = ... RETURNING totalScore
        let totalScore = try player.updateAndFetch(
            db, columns: ["Score"],
            selection: [Column("totalScore")],
            fetch: { statement in
                try Int.fetchOne(statement)
            })
    }
    

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database.

    Precondition

    selection is not empty.
  • Executes an UPDATE ... RETURNING ... statement on all columns, and returns the selected columns from the updated row.

    For example:

    try dbQueue.write { db in
        // UPDATE player SET ... RETURNING totalScore
        let totalScore = try player.updateAndFetch(db, selection: [Column("totalScore")]) { statement in
            try Int.fetchOne(statement)
        }
    }
    

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database.

    Precondition

    selection is not empty.
  • Modifies the record according to the provided modify closure, and executes an UPDATE ... RETURNING ... statement that updates the modified columns, if and only the record was modified. The method returns a new record built from the updated row.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs. PersistenceError.recordNotFound is thrown if the primary key does not match any row in the database and record could not be updated.

    Precondition

    selection is not empty.

Upsert

  • upsert(_:) Extension method

    Executes an INSERT ... ON CONFLICT DO UPDATE statement.

    The upsert behavior is triggered by a violation of any uniqueness constraint on the table (primary key or unique index). In case of violation, all columns but the primary key are overwritten with the inserted values:

    struct Player: Encodable, MutablePersistableRecord {
        var id: Int64
        var name: String
        var score: Int
    }
    
    // INSERT INTO player (id, name, score)
    // VALUES (1, 'Arthur', 1000)
    // ON CONFLICT DO UPDATE SET
    //   name = excluded.name,
    //   score = excluded.score
    var player = Player(id: 1, name: "Arthur", score: 1000)
    try player.upsert(db)
    
  • Executes an INSERT ... ON CONFLICT DO UPDATE ... RETURNING ... statement, and returns the upserted record.

    With default parameters (upsertAndFetch(db)), the upsert behavior is triggered by a violation of any uniqueness constraint on the table (primary key or unique index). In case of violation, all columns but the primary key are overwritten with the inserted values:

    struct Player: Encodable, MutablePersistableRecord {
        var id: Int64
        var name: String
        var score: Int
    }
    
    // INSERT INTO player (id, name, score)
    // VALUES (1, 'Arthur', 1000)
    // ON CONFLICT DO UPDATE SET
    //   name = excluded.name,
    //   score = excluded.score
    // RETURNING *
    var player = Player(id: 1, name: "Arthur", score: 1000)
    let upsertedPlayer = try player.upsertAndFetch(db)
    

    With conflictTarget and assignments arguments, you can further control the upsert behavior. Make sure you check https://www.sqlite.org/lang_UPSERT.html for detailed information.

    The conflict target are the columns of the uniqueness constraint (primary key or unique index) that triggers the upsert. If empty, all uniqueness constraint are considered.

    The assignments describe how to update columns in case of violation of a uniqueness constraint. In the next example, we insert the new vocabulary word “jovial” if that word is not already in the dictionary. If the word is already in the dictionary, it increments the counter, does not overwrite the tainted flag, and overwrites the remaining columns:

    // CREATE TABLE vocabulary(
    //   word TEXT PRIMARY KEY,
    //   kind TEXT NOT NULL,
    //   isTainted BOOLEAN DEFAULT 0,
    //   count INT DEFAULT 1))
    struct Vocabulary: Encodable, MutablePersistableRecord {
        var word: String
        var kind: String
        var isTainted: Bool
    }
    
    // INSERT INTO vocabulary(word, kind, isTainted)
    // VALUES('jovial', 'adjective', 0)
    // ON CONFLICT(word) DO UPDATE SET \
    //   count = count + 1,
    //   kind = excluded.kind
    // RETURNING *
    var vocabulary = Vocabulary(word: "jovial", kind: "adjective", isTainted: false)
    let upserted = try vocabulary.upsertAndFetch(
        db,
        onConflict: ["word"],
        doUpdate: { _ in
            [Column("count") += 1,
             Column("isTainted").noOverwrite]
        })
    

    Throws

    A DatabaseError whenever an SQLite error occurs.
  • Executes an INSERT ... ON CONFLICT DO UPDATE ... RETURNING ... statement, and returns the upserted record.

    See upsertAndFetch(_:onConflict:doUpdate:) for more information about the conflictTarget and assignments parameters.

    Throws

    A DatabaseError whenever an SQLite error occurs.

Existence Check

  • exists(_:) Extension method

    Returns true if and only if the primary key matches a row in the database.

    Throws

    A DatabaseError is thrown whenever an SQLite error occurs.