mirror of https://github.com/AMT-Cheif/drift.git
Support upsert clauses from Dart DSL (#367)
This commit is contained in:
parent
a7ac6db55d
commit
ca0fe1ef55
|
@ -34,8 +34,8 @@ class Batch {
|
||||||
{InsertMode mode}) {
|
{InsertMode mode}) {
|
||||||
_addUpdate(table, UpdateKind.insert);
|
_addUpdate(table, UpdateKind.insert);
|
||||||
final actualMode = mode ?? InsertMode.insert;
|
final actualMode = mode ?? InsertMode.insert;
|
||||||
final context =
|
final context = InsertStatement<Table, D>(_engine, table)
|
||||||
InsertStatement<D>(_engine, table).createContext(row, actualMode);
|
.createContext(row, actualMode);
|
||||||
_addContext(context);
|
_addContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,8 +102,10 @@ mixin QueryEngine on DatabaseConnectionUser {
|
||||||
/// to write data into the [table] by using [InsertStatement.insert].
|
/// to write data into the [table] by using [InsertStatement.insert].
|
||||||
@protected
|
@protected
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
InsertStatement<T> into<T extends DataClass>(TableInfo<Table, T> table) =>
|
InsertStatement<T, D> into<T extends Table, D extends DataClass>(
|
||||||
InsertStatement<T>(_resolvedEngine, table);
|
TableInfo<T, D> table) {
|
||||||
|
return InsertStatement<T, D>(_resolvedEngine, table);
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts an [UpdateStatement] for the given table. You can use that
|
/// Starts an [UpdateStatement] for the given table. You can use that
|
||||||
/// statement to update individual rows in that table by setting a where
|
/// statement to update individual rows in that table by setting a where
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
part of '../query_builder.dart';
|
part of '../query_builder.dart';
|
||||||
|
|
||||||
/// Represents an insert statements
|
/// Represents an insert statements
|
||||||
class InsertStatement<D extends DataClass> {
|
class InsertStatement<T extends Table, D extends DataClass> {
|
||||||
/// The database to use then executing this statement
|
/// The database to use then executing this statement
|
||||||
@protected
|
@protected
|
||||||
final QueryEngine database;
|
final QueryEngine database;
|
||||||
|
|
||||||
/// The table we're inserting into
|
/// The table we're inserting into
|
||||||
@protected
|
@protected
|
||||||
final TableInfo<Table, D> table;
|
final TableInfo<T, D> table;
|
||||||
|
|
||||||
/// Constructs an insert statement from the database and the table. Used
|
/// Constructs an insert statement from the database and the table. Used
|
||||||
/// internally by moor.
|
/// internally by moor.
|
||||||
|
@ -24,6 +24,31 @@ class InsertStatement<D extends DataClass> {
|
||||||
/// primary key already exists. This behavior can be overridden with [mode],
|
/// primary key already exists. This behavior can be overridden with [mode],
|
||||||
/// for instance by using [InsertMode.replace] or [InsertMode.insertOrIgnore].
|
/// for instance by using [InsertMode.replace] or [InsertMode.insertOrIgnore].
|
||||||
///
|
///
|
||||||
|
/// To apply a partial or custom update in case of a conflict, you can also
|
||||||
|
/// use an [upsert clause](https://sqlite.org/lang_UPSERT.html) by using
|
||||||
|
/// [onConflict].
|
||||||
|
/// For instance, you could increase a counter whenever a conflict occurs:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// class Words extends Table {
|
||||||
|
/// TextColumn get word => text()();
|
||||||
|
/// IntColumn get occurrences => integer()();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Future<void> addWord(String word) async {
|
||||||
|
/// await into(words).insert(
|
||||||
|
/// WordsCompanion.insert(word: word, occurrences: 1),
|
||||||
|
/// onConflict: DoUpdate((old) => WordsCompanion.custom(
|
||||||
|
/// occurrences: old.occurrences + Constant(1),
|
||||||
|
/// )),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When calling `addWord` with a word not yet saved, the regular insert will
|
||||||
|
/// write it with one occurrence. If it already exists however, the insert
|
||||||
|
/// behaves like an update incrementing occurrences by one.
|
||||||
|
///
|
||||||
/// If the table contains an auto-increment column, the generated value will
|
/// If the table contains an auto-increment column, the generated value will
|
||||||
/// be returned. If there is no auto-increment column, you can't rely on the
|
/// be returned. If there is no auto-increment column, you can't rely on the
|
||||||
/// return value, but the future will complete with an error if the insert
|
/// return value, but the future will complete with an error if the insert
|
||||||
|
@ -31,8 +56,10 @@ class InsertStatement<D extends DataClass> {
|
||||||
Future<int> insert(
|
Future<int> insert(
|
||||||
Insertable<D> entity, {
|
Insertable<D> entity, {
|
||||||
InsertMode mode,
|
InsertMode mode,
|
||||||
|
DoUpdate<T, D> onConflict,
|
||||||
}) async {
|
}) async {
|
||||||
final ctx = createContext(entity, mode ?? InsertMode.insert);
|
final ctx = createContext(entity, mode ?? InsertMode.insert,
|
||||||
|
onConflict: onConflict);
|
||||||
|
|
||||||
return await database.doWhenOpened((e) async {
|
return await database.doWhenOpened((e) async {
|
||||||
final id = await e.runInsert(ctx.sql, ctx.boundVariables);
|
final id = await e.runInsert(ctx.sql, ctx.boundVariables);
|
||||||
|
@ -46,7 +73,8 @@ class InsertStatement<D extends DataClass> {
|
||||||
/// insert statement fro the [entry] with the [mode].
|
/// insert statement fro the [entry] with the [mode].
|
||||||
///
|
///
|
||||||
/// This method is used internally by moor. Consider using [insert] instead.
|
/// This method is used internally by moor. Consider using [insert] instead.
|
||||||
GenerationContext createContext(Insertable<D> entry, InsertMode mode) {
|
GenerationContext createContext(Insertable<D> entry, InsertMode mode,
|
||||||
|
{DoUpdate onConflict}) {
|
||||||
_validateIntegrity(entry);
|
_validateIntegrity(entry);
|
||||||
|
|
||||||
final rawValues = entry.toColumns(true);
|
final rawValues = entry.toColumns(true);
|
||||||
|
@ -99,6 +127,23 @@ class InsertStatement<D extends DataClass> {
|
||||||
ctx.buffer.write(')');
|
ctx.buffer.write(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onConflict != null) {
|
||||||
|
final updateSet = onConflict._createInsertable(table).toColumns(true);
|
||||||
|
|
||||||
|
ctx.buffer.write(' ON CONFLICT DO UPDATE SET ');
|
||||||
|
|
||||||
|
var first = true;
|
||||||
|
for (final update in updateSet.entries) {
|
||||||
|
final column = escapeIfNeeded(update.key);
|
||||||
|
|
||||||
|
if (!first) ctx.buffer.write(', ');
|
||||||
|
ctx.buffer.write('$column = ');
|
||||||
|
update.value.writeInto(ctx);
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,3 +199,18 @@ const _insertKeywords = <InsertMode, String>{
|
||||||
InsertMode.insertOrFail: 'INSERT OR FAIL',
|
InsertMode.insertOrFail: 'INSERT OR FAIL',
|
||||||
InsertMode.insertOrIgnore: 'INSERT OR IGNORE',
|
InsertMode.insertOrIgnore: 'INSERT OR IGNORE',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 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 extends DataClass> {
|
||||||
|
final Insertable<D> Function(T old) _creator;
|
||||||
|
|
||||||
|
/// For an example, see [InsertStatement.insert].
|
||||||
|
DoUpdate(Insertable<D> Function(T old) update) : _creator = update;
|
||||||
|
|
||||||
|
Insertable<D> _createInsertable(T table) {
|
||||||
|
return _creator(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -145,4 +145,20 @@ void main() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can use an upsert clause', () async {
|
||||||
|
await db.into(db.todosTable).insert(
|
||||||
|
TodosTableCompanion.insert(content: 'my content'),
|
||||||
|
onConflict: DoUpdate((old) {
|
||||||
|
return TodosTableCompanion.custom(
|
||||||
|
content: const Variable('important: ') + old.content);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(executor.runInsert(
|
||||||
|
'INSERT INTO todos (content) VALUES (?) '
|
||||||
|
'ON CONFLICT DO UPDATE SET content = ? || content',
|
||||||
|
argThat(equals(['my content', 'important: '])),
|
||||||
|
));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue