Merge pull request #2349 from FaFre/upsert_do_nothing

Upsert constraint support DO NOTHING
This commit is contained in:
Simon Binder 2023-03-14 17:31:44 +01:00 committed by GitHub
commit fadc30df1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 21 deletions

View File

@ -9,6 +9,7 @@
in the database class, the schema version in the database will now be downgraded.
- When using a drift isolate in the same engine group, errors on the remote end are
reported as-is instead of wrapping them in a `DriftRemoteException`.
- Added support for `DO NOTHING` during upsert operations with constraint violations
## 2.5.0

View File

@ -271,6 +271,29 @@ class InsertStatement<T extends Table, D> {
Insertable<D>? originalEntry,
UpsertClause<T, D>? onConflict,
) {
void writeOnConflictConstraint(List<Column<Object>>? target) {
ctx.buffer.write(' ON CONFLICT(');
final conflictTarget = target ?? table.$primaryKey.toList();
if (conflictTarget.isEmpty) {
throw ArgumentError(
'Table has no primary key, so a conflict target is needed.');
}
var first = true;
for (final target in conflictTarget) {
if (!first) ctx.buffer.write(', ');
// Writing the escaped name directly because it should not have a table
// name in front of it.
ctx.buffer.write(target.escapedName);
first = false;
}
ctx.buffer.write(')');
}
if (onConflict is DoUpdate<T, D>) {
if (onConflict._usesExcludedTable) {
ctx.hasMultipleTables = true;
@ -290,32 +313,15 @@ class InsertStatement<T extends Table, D> {
final updateSet = upsertInsertable.toColumns(true);
ctx.buffer.write(' ON CONFLICT(');
final conflictTarget = onConflict.target ?? table.$primaryKey.toList();
if (conflictTarget.isEmpty) {
throw ArgumentError(
'Table has no primary key, so a conflict target is needed.');
}
var first = true;
for (final target in conflictTarget) {
if (!first) ctx.buffer.write(', ');
// Writing the escaped name directly because it should not have a table
// name in front of it.
ctx.buffer.write(target.escapedName);
first = false;
}
writeOnConflictConstraint(onConflict.target);
if (ctx.dialect == SqlDialect.postgres &&
mode == InsertMode.insertOrIgnore) {
ctx.buffer.write(') DO NOTHING ');
ctx.buffer.write(' DO NOTHING ');
} else {
ctx.buffer.write(') DO UPDATE SET ');
ctx.buffer.write(' DO UPDATE SET ');
first = true;
var first = true;
for (final update in updateSet.entries) {
final column = ctx.identifier(update.key);
@ -337,6 +343,9 @@ class InsertStatement<T extends Table, D> {
for (final clause in onConflict.clauses) {
_writeOnConflict(ctx, mode, originalEntry, clause);
}
} else if (onConflict is DoNothing<T, D>) {
writeOnConflictConstraint(onConflict.target);
ctx.buffer.write(' DO NOTHING');
}
}
@ -510,3 +519,15 @@ class UpsertMultiple<T extends Table, D> extends UpsertClause<T, D> {
/// 03-12).
UpsertMultiple(this.clauses);
}
/// Upsert clause that does nothing on conflict
class DoNothing<T extends Table, D> extends UpsertClause<T, D> {
/// An optional list of columns to serve as an "conflict target", which
/// specifies the uniqueness constraint that will trigger the upsert.
///
/// By default, the primary key of the table will be used.
final List<Column>? target;
/// Creates an upsert clause that does nothing on conflict
DoNothing({this.target});
}

View File

@ -340,6 +340,34 @@ void main() {
));
});
test('can use do nothing on upsert', () async {
await db.into(db.todosTable).insert(
TodosTableCompanion.insert(content: 'my content'),
onConflict: DoNothing(),
);
verify(executor.runInsert(
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("id") DO NOTHING',
argThat(equals(['my content'])),
));
});
test('can use a custom conflict clause with do nothing', () async {
await db.into(db.todosTable).insert(
TodosTableCompanion.insert(content: 'my content'),
onConflict: DoNothing(
target: [db.todosTable.content],
),
);
verify(executor.runInsert(
'INSERT INTO "todos" ("content") VALUES (?) '
'ON CONFLICT("content") DO NOTHING',
argThat(equals(['my content'])),
));
});
test('insertOnConflictUpdate', () async {
when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(3));