Add example with upsert conflict target to docs

This commit is contained in:
Simon Binder 2023-11-16 21:16:51 +01:00
parent 2ac81ddb99
commit 407a40fae1
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
3 changed files with 512 additions and 22 deletions

View File

@ -0,0 +1,54 @@
import 'package:drift/drift.dart';
import 'package:drift/internal/modular.dart';
import 'upserts.drift.dart';
// #docregion words-table
class Words extends Table {
TextColumn get word => text()();
IntColumn get usages => integer().withDefault(const Constant(1))();
@override
Set<Column> get primaryKey => {word};
}
// #enddocregion words-table
// #docregion upsert-target
class MatchResults extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get teamA => text()();
TextColumn get teamB => text()();
BoolColumn get teamAWon => boolean()();
@override
List<Set<Column<Object>>>? get uniqueKeys => [
{teamA, teamB}
];
}
// #enddocregion upsert-target
extension DocumentationSnippets on ModularAccessor {
$WordsTable get words => throw 'stub';
$MatchResultsTable get matches => throw 'stub';
// #docregion track-word
Future<void> trackWord(String word) {
return into(words).insert(
WordsCompanion.insert(word: word),
onConflict: DoUpdate(
(old) => WordsCompanion.custom(usages: old.usages + Constant(1))),
);
}
// #enddocregion track-word
// #docregion upsert-target
Future<void> insertMatch(String teamA, String teamB, bool teamAWon) {
final data = MatchResultsCompanion.insert(
teamA: teamA, teamB: teamB, teamAWon: teamAWon);
return into(matches).insert(data,
onConflict:
DoUpdate((old) => data, target: [matches.teamA, matches.teamB]));
}
// #enddocregion upsert-target
}

View File

@ -0,0 +1,446 @@
// ignore_for_file: type=lint
import 'package:drift/drift.dart' as i0;
import 'package:drift_docs/snippets/modular/upserts.drift.dart' as i1;
import 'package:drift_docs/snippets/modular/upserts.dart' as i2;
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
class $WordsTable extends i2.Words with i0.TableInfo<$WordsTable, i1.Word> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$WordsTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _wordMeta =
const i0.VerificationMeta('word');
@override
late final i0.GeneratedColumn<String> word = i0.GeneratedColumn<String>(
'word', aliasedName, false,
type: i0.DriftSqlType.string, requiredDuringInsert: true);
static const i0.VerificationMeta _usagesMeta =
const i0.VerificationMeta('usages');
@override
late final i0.GeneratedColumn<int> usages = i0.GeneratedColumn<int>(
'usages', aliasedName, false,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const i3.Constant(1));
@override
List<i0.GeneratedColumn> get $columns => [word, usages];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'words';
@override
i0.VerificationContext validateIntegrity(i0.Insertable<i1.Word> instance,
{bool isInserting = false}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('word')) {
context.handle(
_wordMeta, word.isAcceptableOrUnknown(data['word']!, _wordMeta));
} else if (isInserting) {
context.missing(_wordMeta);
}
if (data.containsKey('usages')) {
context.handle(_usagesMeta,
usages.isAcceptableOrUnknown(data['usages']!, _usagesMeta));
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {word};
@override
i1.Word map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.Word(
word: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}word'])!,
usages: attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}usages'])!,
);
}
@override
$WordsTable createAlias(String alias) {
return $WordsTable(attachedDatabase, alias);
}
}
class Word extends i0.DataClass implements i0.Insertable<i1.Word> {
final String word;
final int usages;
const Word({required this.word, required this.usages});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['word'] = i0.Variable<String>(word);
map['usages'] = i0.Variable<int>(usages);
return map;
}
i1.WordsCompanion toCompanion(bool nullToAbsent) {
return i1.WordsCompanion(
word: i0.Value(word),
usages: i0.Value(usages),
);
}
factory Word.fromJson(Map<String, dynamic> json,
{i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return Word(
word: serializer.fromJson<String>(json['word']),
usages: serializer.fromJson<int>(json['usages']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'word': serializer.toJson<String>(word),
'usages': serializer.toJson<int>(usages),
};
}
i1.Word copyWith({String? word, int? usages}) => i1.Word(
word: word ?? this.word,
usages: usages ?? this.usages,
);
@override
String toString() {
return (StringBuffer('Word(')
..write('word: $word, ')
..write('usages: $usages')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(word, usages);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.Word &&
other.word == this.word &&
other.usages == this.usages);
}
class WordsCompanion extends i0.UpdateCompanion<i1.Word> {
final i0.Value<String> word;
final i0.Value<int> usages;
final i0.Value<int> rowid;
const WordsCompanion({
this.word = const i0.Value.absent(),
this.usages = const i0.Value.absent(),
this.rowid = const i0.Value.absent(),
});
WordsCompanion.insert({
required String word,
this.usages = const i0.Value.absent(),
this.rowid = const i0.Value.absent(),
}) : word = i0.Value(word);
static i0.Insertable<i1.Word> custom({
i0.Expression<String>? word,
i0.Expression<int>? usages,
i0.Expression<int>? rowid,
}) {
return i0.RawValuesInsertable({
if (word != null) 'word': word,
if (usages != null) 'usages': usages,
if (rowid != null) 'rowid': rowid,
});
}
i1.WordsCompanion copyWith(
{i0.Value<String>? word, i0.Value<int>? usages, i0.Value<int>? rowid}) {
return i1.WordsCompanion(
word: word ?? this.word,
usages: usages ?? this.usages,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (word.present) {
map['word'] = i0.Variable<String>(word.value);
}
if (usages.present) {
map['usages'] = i0.Variable<int>(usages.value);
}
if (rowid.present) {
map['rowid'] = i0.Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('WordsCompanion(')
..write('word: $word, ')
..write('usages: $usages, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
class $MatchResultsTable extends i2.MatchResults
with i0.TableInfo<$MatchResultsTable, i1.MatchResult> {
@override
final i0.GeneratedDatabase attachedDatabase;
final String? _alias;
$MatchResultsTable(this.attachedDatabase, [this._alias]);
static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id');
@override
late final i0.GeneratedColumn<int> id = i0.GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: i0.DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
i0.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const i0.VerificationMeta _teamAMeta =
const i0.VerificationMeta('teamA');
@override
late final i0.GeneratedColumn<String> teamA = i0.GeneratedColumn<String>(
'team_a', aliasedName, false,
type: i0.DriftSqlType.string, requiredDuringInsert: true);
static const i0.VerificationMeta _teamBMeta =
const i0.VerificationMeta('teamB');
@override
late final i0.GeneratedColumn<String> teamB = i0.GeneratedColumn<String>(
'team_b', aliasedName, false,
type: i0.DriftSqlType.string, requiredDuringInsert: true);
static const i0.VerificationMeta _teamAWonMeta =
const i0.VerificationMeta('teamAWon');
@override
late final i0.GeneratedColumn<bool> teamAWon = i0.GeneratedColumn<bool>(
'team_a_won', aliasedName, false,
type: i0.DriftSqlType.bool,
requiredDuringInsert: true,
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
'CHECK ("team_a_won" IN (0, 1))'));
@override
List<i0.GeneratedColumn> get $columns => [id, teamA, teamB, teamAWon];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'match_results';
@override
i0.VerificationContext validateIntegrity(
i0.Insertable<i1.MatchResult> instance,
{bool isInserting = false}) {
final context = i0.VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('team_a')) {
context.handle(
_teamAMeta, teamA.isAcceptableOrUnknown(data['team_a']!, _teamAMeta));
} else if (isInserting) {
context.missing(_teamAMeta);
}
if (data.containsKey('team_b')) {
context.handle(
_teamBMeta, teamB.isAcceptableOrUnknown(data['team_b']!, _teamBMeta));
} else if (isInserting) {
context.missing(_teamBMeta);
}
if (data.containsKey('team_a_won')) {
context.handle(_teamAWonMeta,
teamAWon.isAcceptableOrUnknown(data['team_a_won']!, _teamAWonMeta));
} else if (isInserting) {
context.missing(_teamAWonMeta);
}
return context;
}
@override
Set<i0.GeneratedColumn> get $primaryKey => {id};
@override
List<Set<i0.GeneratedColumn>> get uniqueKeys => [
{teamA, teamB},
];
@override
i1.MatchResult map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return i1.MatchResult(
id: attachedDatabase.typeMapping
.read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!,
teamA: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}team_a'])!,
teamB: attachedDatabase.typeMapping
.read(i0.DriftSqlType.string, data['${effectivePrefix}team_b'])!,
teamAWon: attachedDatabase.typeMapping
.read(i0.DriftSqlType.bool, data['${effectivePrefix}team_a_won'])!,
);
}
@override
$MatchResultsTable createAlias(String alias) {
return $MatchResultsTable(attachedDatabase, alias);
}
}
class MatchResult extends i0.DataClass
implements i0.Insertable<i1.MatchResult> {
final int id;
final String teamA;
final String teamB;
final bool teamAWon;
const MatchResult(
{required this.id,
required this.teamA,
required this.teamB,
required this.teamAWon});
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
map['id'] = i0.Variable<int>(id);
map['team_a'] = i0.Variable<String>(teamA);
map['team_b'] = i0.Variable<String>(teamB);
map['team_a_won'] = i0.Variable<bool>(teamAWon);
return map;
}
i1.MatchResultsCompanion toCompanion(bool nullToAbsent) {
return i1.MatchResultsCompanion(
id: i0.Value(id),
teamA: i0.Value(teamA),
teamB: i0.Value(teamB),
teamAWon: i0.Value(teamAWon),
);
}
factory MatchResult.fromJson(Map<String, dynamic> json,
{i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return MatchResult(
id: serializer.fromJson<int>(json['id']),
teamA: serializer.fromJson<String>(json['teamA']),
teamB: serializer.fromJson<String>(json['teamB']),
teamAWon: serializer.fromJson<bool>(json['teamAWon']),
);
}
@override
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'teamA': serializer.toJson<String>(teamA),
'teamB': serializer.toJson<String>(teamB),
'teamAWon': serializer.toJson<bool>(teamAWon),
};
}
i1.MatchResult copyWith(
{int? id, String? teamA, String? teamB, bool? teamAWon}) =>
i1.MatchResult(
id: id ?? this.id,
teamA: teamA ?? this.teamA,
teamB: teamB ?? this.teamB,
teamAWon: teamAWon ?? this.teamAWon,
);
@override
String toString() {
return (StringBuffer('MatchResult(')
..write('id: $id, ')
..write('teamA: $teamA, ')
..write('teamB: $teamB, ')
..write('teamAWon: $teamAWon')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, teamA, teamB, teamAWon);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is i1.MatchResult &&
other.id == this.id &&
other.teamA == this.teamA &&
other.teamB == this.teamB &&
other.teamAWon == this.teamAWon);
}
class MatchResultsCompanion extends i0.UpdateCompanion<i1.MatchResult> {
final i0.Value<int> id;
final i0.Value<String> teamA;
final i0.Value<String> teamB;
final i0.Value<bool> teamAWon;
const MatchResultsCompanion({
this.id = const i0.Value.absent(),
this.teamA = const i0.Value.absent(),
this.teamB = const i0.Value.absent(),
this.teamAWon = const i0.Value.absent(),
});
MatchResultsCompanion.insert({
this.id = const i0.Value.absent(),
required String teamA,
required String teamB,
required bool teamAWon,
}) : teamA = i0.Value(teamA),
teamB = i0.Value(teamB),
teamAWon = i0.Value(teamAWon);
static i0.Insertable<i1.MatchResult> custom({
i0.Expression<int>? id,
i0.Expression<String>? teamA,
i0.Expression<String>? teamB,
i0.Expression<bool>? teamAWon,
}) {
return i0.RawValuesInsertable({
if (id != null) 'id': id,
if (teamA != null) 'team_a': teamA,
if (teamB != null) 'team_b': teamB,
if (teamAWon != null) 'team_a_won': teamAWon,
});
}
i1.MatchResultsCompanion copyWith(
{i0.Value<int>? id,
i0.Value<String>? teamA,
i0.Value<String>? teamB,
i0.Value<bool>? teamAWon}) {
return i1.MatchResultsCompanion(
id: id ?? this.id,
teamA: teamA ?? this.teamA,
teamB: teamB ?? this.teamB,
teamAWon: teamAWon ?? this.teamAWon,
);
}
@override
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
final map = <String, i0.Expression>{};
if (id.present) {
map['id'] = i0.Variable<int>(id.value);
}
if (teamA.present) {
map['team_a'] = i0.Variable<String>(teamA.value);
}
if (teamB.present) {
map['team_b'] = i0.Variable<String>(teamB.value);
}
if (teamAWon.present) {
map['team_a_won'] = i0.Variable<bool>(teamAWon.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('MatchResultsCompanion(')
..write('id: $id, ')
..write('teamA: $teamA, ')
..write('teamB: $teamB, ')
..write('teamAWon: $teamAWon')
..write(')'))
.toString();
}
}

View File

@ -109,7 +109,9 @@ This makes them suitable for bulk insert or update operations.
### Upserts ### Upserts
Upserts are a feature from newer sqlite3 versions that allows an insert to {% assign upserts = "package:drift_docs/snippets/modular/upserts.dart.excerpt.json" | readString | json_decode %}
Upserts are a feature from newer sqlite3 versions that allows an insert to
behave like an update if a conflicting row already exists. behave like an update if a conflicting row already exists.
This allows us to create or override an existing row when its primary key is This allows us to create or override an existing row when its primary key is
@ -129,35 +131,20 @@ Future<int> createOrUpdateUser(User user) {
} }
``` ```
When calling `createOrUpdateUser()` with an email address that already exists, When calling `createOrUpdateUser()` with an email address that already exists,
that user's name will be updated. Otherwise, a new user will be inserted into that user's name will be updated. Otherwise, a new user will be inserted into
the database. the database.
Inserts can also be used with more advanced queries. For instance, let's say Inserts can also be used with more advanced queries. For instance, let's say
we're building a dictionary and want to keep track of how many times we we're building a dictionary and want to keep track of how many times we
encountered a word. A table for that might look like encountered a word. A table for that might look like
```dart {% include "blocks/snippet" snippets = upserts name = "words-table" %}
class Words extends Table {
TextColumn get word => text()();
IntColumn get usages => integer().withDefault(const Constant(1))();
@override
Set<Column> get primaryKey => {word};
}
```
By using a custom upserts, we can insert a new word or increment its `usages` By using a custom upserts, we can insert a new word or increment its `usages`
counter if it already exists: counter if it already exists:
```dart {% include "blocks/snippet" snippets = upserts name = "track-word" %}
Future<void> trackWord(String word) {
return into(words).insert(
WordsCompanion.insert(word: word),
onConflict: DoUpdate((old) => WordsCompanion.custom(usages: old.usages + Constant(1))),
);
}
```
{% block "blocks/alert" title="Unique constraints and conflict targets" %} {% block "blocks/alert" title="Unique constraints and conflict targets" %}
Both `insertOnConflictUpdate` and `onConflict: DoUpdate` use an `DO UPDATE` Both `insertOnConflictUpdate` and `onConflict: DoUpdate` use an `DO UPDATE`
@ -165,7 +152,10 @@ upsert in sql. This requires us to provide a so-called "conflict target", a
set of columns to check for uniqueness violations. By default, drift will use set of columns to check for uniqueness violations. By default, drift will use
the table's primary key as conflict target. That works in most cases, but if the table's primary key as conflict target. That works in most cases, but if
you have custom `UNIQUE` constraints on some columns, you'll need to use you have custom `UNIQUE` constraints on some columns, you'll need to use
the `target` parameter on `DoUpdate` in Dart to include those columns. the `target` parameter on `DoUpdate` in Dart to include those columns:
{% include "blocks/snippet" snippets = upserts name = "upsert-target" %}
{% endblock %} {% endblock %}
Note that this requires a fairly recent sqlite3 version (3.24.0) that might not Note that this requires a fairly recent sqlite3 version (3.24.0) that might not