mirror of https://github.com/AMT-Cheif/drift.git
Support multiple upsert clauses
This commit is contained in:
parent
dcb1c4776b
commit
a1c9a5933d
|
@ -37,7 +37,7 @@ class Batch {
|
|||
/// See also:
|
||||
/// - [InsertStatement.insert], which would be used outside a [Batch].
|
||||
void insert<T extends Table, D>(TableInfo<T, D> table, Insertable<D> row,
|
||||
{InsertMode? mode, DoUpdate<T, D>? onConflict}) {
|
||||
{InsertMode? mode, UpsertClause<T, D>? onConflict}) {
|
||||
_addUpdate(table, UpdateKind.insert);
|
||||
final actualMode = mode ?? InsertMode.insert;
|
||||
final context = InsertStatement<Table, D>(_user, table)
|
||||
|
@ -59,7 +59,7 @@ class Batch {
|
|||
/// support it. For details and examples, see [InsertStatement.insert].
|
||||
void insertAll<T extends Table, D>(
|
||||
TableInfo<T, D> table, List<Insertable<D>> rows,
|
||||
{InsertMode? mode, DoUpdate<T, D>? onConflict}) {
|
||||
{InsertMode? mode, UpsertClause<T, D>? onConflict}) {
|
||||
for (final row in rows) {
|
||||
insert<T, D>(table, row, mode: mode, onConflict: onConflict);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ class InsertStatement<T extends Table, D> {
|
|||
Future<int> insert(
|
||||
Insertable<D> entity, {
|
||||
InsertMode? mode,
|
||||
DoUpdate<T, D>? onConflict,
|
||||
UpsertClause<T, D>? onConflict,
|
||||
}) async {
|
||||
final ctx = createContext(entity, mode ?? InsertMode.insert,
|
||||
onConflict: onConflict);
|
||||
|
@ -81,7 +81,7 @@ class InsertStatement<T extends Table, D> {
|
|||
/// this method, make sure that you have a recent sqlite3 version available.
|
||||
/// This is the case with `sqlite3_flutter_libs`.
|
||||
Future<D> insertReturning(Insertable<D> entity,
|
||||
{InsertMode? mode, DoUpdate<T, D>? onConflict}) async {
|
||||
{InsertMode? mode, UpsertClause<T, D>? onConflict}) async {
|
||||
final ctx = createContext(entity, mode ?? InsertMode.insert,
|
||||
onConflict: onConflict, returning: true);
|
||||
|
||||
|
@ -113,7 +113,7 @@ class InsertStatement<T extends Table, D> {
|
|||
///
|
||||
/// This method is used internally by moor. Consider using [insert] instead.
|
||||
GenerationContext createContext(Insertable<D> entry, InsertMode mode,
|
||||
{DoUpdate<T, D>? onConflict, bool returning = false}) {
|
||||
{UpsertClause<T, D>? onConflict, bool returning = false}) {
|
||||
_validateIntegrity(entry);
|
||||
|
||||
final rawValues = entry.toColumns(true);
|
||||
|
@ -148,7 +148,7 @@ class InsertStatement<T extends Table, D> {
|
|||
writeInsertable(ctx, map);
|
||||
}
|
||||
|
||||
if (onConflict != null) {
|
||||
void writeDoUpdate(DoUpdate<T, D> onConflict) {
|
||||
final upsertInsertable = onConflict._createInsertable(table.asDslTable);
|
||||
|
||||
if (!identical(entry, upsertInsertable)) {
|
||||
|
@ -194,6 +194,12 @@ class InsertStatement<T extends Table, D> {
|
|||
}
|
||||
}
|
||||
|
||||
if (onConflict is DoUpdate<T, D>) {
|
||||
writeDoUpdate(onConflict);
|
||||
} else if (onConflict is UpsertMultiple<T, D>) {
|
||||
onConflict.clauses.forEach(writeDoUpdate);
|
||||
}
|
||||
|
||||
if (returning) {
|
||||
ctx.buffer.write(' RETURNING *');
|
||||
}
|
||||
|
@ -278,11 +284,17 @@ const _insertKeywords = <InsertMode, String>{
|
|||
InsertMode.insertOrIgnore: 'INSERT OR IGNORE',
|
||||
};
|
||||
|
||||
/// A upsert clause controls how to behave when a uniqueness constraint is
|
||||
/// violated during an insert.
|
||||
///
|
||||
/// Typically, one would use [DoUpdate] to run an update instead in this case.
|
||||
abstract class UpsertClause<T extends Table, D> {}
|
||||
|
||||
/// A [DoUpdate] upsert clause can be used to insert or update a custom
|
||||
/// companion when the underlying companion already exists.
|
||||
///
|
||||
/// For an example, see [InsertStatement.insert].
|
||||
class DoUpdate<T extends Table, D> {
|
||||
class DoUpdate<T extends Table, D> extends UpsertClause<T, D> {
|
||||
final Insertable<D> Function(T old) _creator;
|
||||
|
||||
/// An optional list of columns to serve as an "conflict target", which
|
||||
|
@ -299,3 +311,19 @@ class DoUpdate<T extends Table, D> {
|
|||
return _creator(table);
|
||||
}
|
||||
}
|
||||
|
||||
/// Upsert clause that consists of multiple [clauses].
|
||||
///
|
||||
/// The first [DoUpdate.target] matched by this upsert will be run.
|
||||
class UpsertMultiple<T extends Table, D> extends UpsertClause<T, D> {
|
||||
/// All [DoUpdate] clauses that are part of this upsert.
|
||||
///
|
||||
/// The first clause with a matching [DoUpdate.target] will be considered.
|
||||
final List<DoUpdate<T, D>> clauses;
|
||||
|
||||
/// Creates an upsert consisting of multiple [DoUpdate] clauses.
|
||||
///
|
||||
/// This requires a fairly recent sqlite3 version (3.35.0, released on 2021-
|
||||
/// 03-12).
|
||||
UpsertMultiple(this.clauses);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'package:test/test.dart';
|
|||
|
||||
import 'data/tables/todos.dart';
|
||||
import 'data/utils/mocks.dart';
|
||||
import 'skips.dart';
|
||||
|
||||
void main() {
|
||||
late TodoDb db;
|
||||
|
@ -93,7 +92,7 @@ void main() {
|
|||
{const TableUpdate('users', kind: UpdateKind.insert)}));
|
||||
|
||||
expect(user.id, 5);
|
||||
}, skip: onNoReturningSupport());
|
||||
});
|
||||
|
||||
group('enforces integrity', () {
|
||||
test('for regular inserts', () async {
|
||||
|
@ -206,6 +205,37 @@ void main() {
|
|||
));
|
||||
});
|
||||
|
||||
test(
|
||||
'can use multiple upsert targets',
|
||||
() async {
|
||||
await db
|
||||
.into(db.todosTable)
|
||||
.insert(TodosTableCompanion.insert(content: 'my content'),
|
||||
onConflict: UpsertMultiple([
|
||||
DoUpdate(
|
||||
(old) {
|
||||
return TodosTableCompanion.custom(
|
||||
content: const Variable('important: ') + old.content);
|
||||
},
|
||||
),
|
||||
DoUpdate(
|
||||
(old) {
|
||||
return TodosTableCompanion.custom(
|
||||
content: const Variable('second: ') + old.content);
|
||||
},
|
||||
target: [db.todosTable.content],
|
||||
),
|
||||
]));
|
||||
|
||||
verify(executor.runInsert(
|
||||
'INSERT INTO todos (content) VALUES (?) '
|
||||
'ON CONFLICT(id) DO UPDATE SET content = ? || content '
|
||||
'ON CONFLICT(content) DO UPDATE SET content = ? || content',
|
||||
argThat(equals(['my content', 'important: ', 'second: '])),
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
test('can use a custom conflict clause', () async {
|
||||
await db.into(db.todosTable).insert(
|
||||
TodosTableCompanion.insert(content: 'my content'),
|
||||
|
|
|
@ -38,5 +38,5 @@ void main() {
|
|||
priority: CategoryPriority.low,
|
||||
),
|
||||
);
|
||||
}, skip: onNoReturningSupport());
|
||||
}, skip: ifOlderThanSqlite335());
|
||||
}
|
||||
|
|
|
@ -118,5 +118,5 @@ void main() {
|
|||
),
|
||||
);
|
||||
});
|
||||
}, skip: onNoReturningSupport());
|
||||
}, skip: ifOlderThanSqlite335());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:sqlite3/sqlite3.dart';
|
||||
|
||||
String? onNoReturningSupport() => sqlite3.version.versionNumber > 3035000
|
||||
String? ifOlderThanSqlite335() => sqlite3.version.versionNumber > 3035000
|
||||
? null
|
||||
: 'RETURNING not supported by sqlite version ${sqlite3.version.libVersion}';
|
||||
|
|
Loading…
Reference in New Issue