You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+105-6Lines changed: 105 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2924,6 +2924,8 @@ try place.delete(db)
2924
2924
let exists =try place.exists(db)
2925
2925
```
2926
2926
2927
+
See [Upsert](#upsert) below for more information about upserts.
2928
+
2927
2929
**The [TableRecord] protocol comes with batch operations**:
2928
2930
2929
2931
```swift
@@ -2946,17 +2948,111 @@ For more information about batch updates, see [Update Requests](#update-requests
2946
2948
2947
2949
- `save` makes sure your values are stored in the database. It performs an UPDATE if the record has a non-null primary key, and then, if no row was modified, an INSERT. It directly performs an INSERT if the record has no primary key, or a null primary key.
- `delete` and `deleteOne` returns whether a database row was deleted or not. `deleteAll` returns the number of deleted rows. `updateAll` returns the number of updated rows. `updateChanges` returns whether a database row was updated or not.
2952
2952
2953
2953
**All primary keys are supported**, including composite primary keys that span several columns, and the [implicit rowid primary key](#the-implicit-rowid-primary-key).
2954
2954
2955
2955
**To customize persistence methods**, you provide [Persistence Callbacks], described below. Do not attempt at overriding the ready-made persistence methods.
2956
2956
2957
+
### Upsert
2958
+
2959
+
[UPSERT](https://www.sqlite.org/lang_UPSERT.html) is an SQLite feature that causes an INSERT to behave as an UPDATE or a no-op if the INSERT would violate a uniqueness constraint (primary key or unique index).
2960
+
2961
+
>**Note**: Upsert apis are available from SQLite 3.35.0+: iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, or with a [custom SQLite build] or [SQLCipher](#encryption).
2962
+
>
2963
+
>**Note**: With regard to [persistence callbacks](#available-callbacks), an upsert behaves exactly like an insert. In particular: the `aroundInsert(_:)` and `didInsert(_:)` callbacks reports the rowid of the inserted or updated row; `willUpdate`, `aroundUdate`, `didUdate` are not called.
2964
+
2965
+
[PersistableRecord] provides three upsert methods:
2966
+
2967
+
- `upsert(_:)`
2968
+
2969
+
Inserts or updates a record.
2970
+
2971
+
The upsert behavior is triggered by a violation ofany uniqueness constraint on the table (primary key or unique index). In caseof conflict, all columns but the primary key are overwritten with the inserted values:
2972
+
2973
+
```swift
2974
+
struct Player: Encodable, PersistableRecord {
2975
+
var id: Int64
2976
+
var name: String
2977
+
var score: Int
2978
+
}
2979
+
2980
+
// INSERT INTO player (id, name, score)
2981
+
// VALUES (1, 'Arthur', 1000)
2982
+
// ON CONFLICT DO UPDATE SET
2983
+
// name = excluded.name,
2984
+
// score = excluded.score
2985
+
let player =Player(id: 1, name: "Arthur", score: 1000)
Inserts or updates a record, and returns the upserted record.
2992
+
2993
+
The `onConflict` and `doUpdate` arguments let you further control the upsert behavior. Make sure you check the [SQLite UPSERT documentation](https://www.sqlite.org/lang_UPSERT.html) for detailed information.
2994
+
2995
+
- `onConflict`: the "conflict target"is the array of columns in the uniqueness constraint (primary key or unique index) that triggers the upsert.
2996
+
2997
+
If empty (the default), all uniqueness constraint are considered.
2998
+
2999
+
- `doUpdate`: a closure that returns columns assignments to perform incaseof conflict. Other columns are overwritten with the inserted values.
3000
+
3001
+
By default, all inserted columns but the primary key and the conflict target are overwritten.
3002
+
3003
+
In the example below, we upsert the new vocabulary word "jovial". It is inserted if that word is not already in the dictionary. Otherwise, `count` is incremented, `isTainted` is not overwritten, and `kind` is overwritten:
3004
+
3005
+
```swift
3006
+
// CREATE TABLE vocabulary(
3007
+
// word TEXT NOT NULL PRIMARY KEY,
3008
+
// kind TEXT NOT NULL,
3009
+
// isTainted BOOLEAN DEFAULT 0,
3010
+
// count INT DEFAULT 1))
3011
+
struct Vocabulary: Encodable, PersistableRecord {
3012
+
var word: String
3013
+
var kind: String
3014
+
var isTainted: Bool
3015
+
}
3016
+
3017
+
// INSERT INTO vocabulary(word, kind, isTainted)
3018
+
// VALUES('jovial', 'adjective', 0)
3019
+
// ON CONFLICT(word) DO UPDATE SET \
3020
+
// count = count + 1, -- on conflict, count is incremented
3021
+
// kind = excluded.kind -- on conflict, kind is overwritten
3022
+
// RETURNING *
3023
+
let vocabulary =Vocabulary(word: "jovial", kind: "adjective", isTainted: false)
3024
+
let upserted =try vocabulary.upsertAndFetch(
3025
+
db, onConflict: ["word"],
3026
+
doUpdate: { _in
3027
+
[Column("count") +=1, // on conflict, count is incremented
3028
+
Column("isTainted").noOverwrite] // on conflict, isTainted is NOT overwritten
3029
+
})
3030
+
```
3031
+
3032
+
The `doUpdate` closure accepts an `excluded` TableAlias argument that refers to the inserted values that trigger the conflict. You can use it to specify an explicit overwrite, or to perform a computation. In the next example, the upsert keeps the maximum date incaseof conflict:
3033
+
3034
+
```swift
3035
+
// INSERT INTO message(id, text, date)
3036
+
// VALUES(...)
3037
+
// ON CONFLICT DO UPDATE SET \
3038
+
// text = excluded.text,
3039
+
// date = MAX(date, excluded.date)
3040
+
// RETURNING *
3041
+
let upserted =try message.upsertAndFetch(doUpdate: { excluded in
- `upsertAndFetch(_:as:onConflict:doUpdate:)` (does not require [FetchableRecord] conformance)
3048
+
3049
+
This method is identical to `upsertAndFetch(_:onConflict:doUpdate:)` described above, but you can provide a distinct [FetchableRecord] record type as a result, in order to specify the returned columns.
3050
+
2957
3051
### Persistence Methods and the `RETURNING` clause
2958
3052
2959
-
SQLite 3.35.0+is able to return values from a inserted or updated row, with the [`RETURNING` clause](https://www.sqlite.org/lang_returning.html) (available from iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, or with a [custom SQLite build]).
3053
+
SQLite is able to return values from a inserted, updated, or deleted row, with the [`RETURNING` clause](https://www.sqlite.org/lang_returning.html).
3054
+
3055
+
>**Note**: Support for the `RETURNING` clause is available from SQLite 3.35.0+: iOS 15.0+, macOS 12.0+, tvOS 15.0+, watchOS 8.0+, or with a [custom SQLite build] or [SQLCipher](#encryption).
2960
3056
2961
3057
The `RETURNING` clause helps dealing with database features such as auto-incremented ids, default values, and [generated columns](https://sqlite.org/gencol.html). You can, for example, insert a few columns and fetch the default or generated ones in one step.
2962
3058
@@ -3021,13 +3117,15 @@ try dbQueue.write { db in
3021
3117
}
3022
3118
```
3023
3119
3024
-
There are other similar persistence methods, such as `saveAndFetch`, `updateAndFetch`, `updateChangesAndFetch`, etc. They all behave like `save`, `update`, `updateChanges` (see [Persistence Methods] and the [`updateChanges` methods](#the-updatechanges-methods)), except that they return saved values. For example:
3120
+
There are other similar persistence methods, such as `upsertAndFetch`, `saveAndFetch`, `updateAndFetch`, `updateChangesAndFetch`, etc. They all behave like `upsert`, `save`, `update`, `updateChanges`, except that they return saved values. For example:
3025
3121
3026
3122
```swift
3027
3123
// Save and return the saved player
3028
3124
let savedPlayer =try player.saveAndFetch(db)
3029
3125
```
3030
3126
3127
+
See [Persistence Methods], [Upsert](#upsert), and [`updateChanges` methods](#the-updatechanges-methods) for more information.
3128
+
3031
3129
**Batch operations** can return updated or deleted values:
3032
3130
3033
3131
>**Warning**: Make sure you check the [documentation of the `RETURNING` clause](https://www.sqlite.org/lang_returning.html), which describes important limitations and caveats for batch operations.
Here is a list with all the available callbacks, listed in the same order in which they will get called during the respective operations:
3218
+
Here is a list with all the available [persistence callbacks], listed in the same order in which they will get called during the respective operations:
3121
3219
3122
3220
- Inserting a record (all `record.insert` and `record.upsert` methods)
3123
3221
- `willSave`
@@ -3140,7 +3238,7 @@ Here is a list with all the available callbacks, listed in the same order in whi
3140
3238
- `aroundDelete`
3141
3239
- `didDelete`
3142
3240
3143
-
Make sure you provide implementations that match the exact callback signatures. When in doubt, check the [reference](http://groue.github.io/GRDB.swift/docs/6.0.0-beta/Protocols/MutablePersistableRecord.html).
3241
+
For detailed information about eachcallback, check the [reference](http://groue.github.io/GRDB.swift/docs/6.0.0-beta/Protocols/MutablePersistableRecord.html).
3144
3242
3145
3243
In the `MutablePersistableRecord` protocol, `willInsert` and `didInsert` are mutating methods. In `PersistableRecord`, they are not mutating.
3146
3244
@@ -8704,6 +8802,7 @@ This chapter was renamed to [Embedding SQL in Query Interface Requests].
0 commit comments