Instance Methodgrdb 7.3.0GRDB

upsertAndFetch(_:onConflict:doUpdate:)

Executes an INSERT ON CONFLICT DO UPDATE RETURNING statement, and returns the upserted record.

PersistableRecord+Upsert.swift:306
iOS
15+
macOS
12+
tvOS
15+
watchOS
8+
func upsertAndFetch(_ db: Database, onConflict conflictTarget: [String] = [], doUpdate assignments: ((_ excluded: TableAlias) -> [ColumnAssignment])? = nil) throws -> Self where Self : FetchableRecord

Parameters

db

A database connection.

conflictTarget

The conflict target.

assignments

An optional function that returns an array of ColumnAssignment. In case of violation of a uniqueness constraints, these assignments are performed, and remaining columns are overwritten by inserted values.

Returns

The upserted record.

Throws

A DatabaseError whenever an SQLite error occurs, or any error thrown by the persistence callbacks defined by the record type.

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, PersistableRecord {
    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 *
let 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, PersistableRecord {
    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 *
let vocabulary = Vocabulary(word: "jovial", kind: "adjective", isTainted: false)
let upserted = try vocabulary.upsertAndFetch(
    db,
    onConflict: ["word"],
    doUpdate: { _ in
        [Column("count") += 1,
         Column("isTainted").noOverwrite]
    })