From 28d465f69c3e7235539766483c6725d347ea4f03 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 5 Oct 2021 18:25:53 +0300 Subject: [PATCH 1/4] implements where for onConflict add test for where for onConflict --- .../query_builder/statements/insert.dart | 21 ++++- moor/test/insert_test.dart | 83 +++++++++++++++++++ .../insert_integration_test.dart | 50 +++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/moor/lib/src/runtime/query_builder/statements/insert.dart b/moor/lib/src/runtime/query_builder/statements/insert.dart index 12250034..70811bb3 100644 --- a/moor/lib/src/runtime/query_builder/statements/insert.dart +++ b/moor/lib/src/runtime/query_builder/statements/insert.dart @@ -197,6 +197,13 @@ class InsertStatement { first = false; } + + if (onConflict._where != null) { + ctx.writeWhitespace(); + final where = onConflict._where!( + table.asDslTable, table.createAlias('excluded').asDslTable); + where.writeInto(ctx); + } } if (onConflict is DoUpdate) { @@ -301,6 +308,8 @@ abstract class UpsertClause {} /// For an example, see [InsertStatement.insert]. class DoUpdate extends UpsertClause { final Insertable Function(T old, T excluded) _creator; + final Where Function(T old, T excluded)? _where; + final bool _usesExcludedTable; /// An optional list of columns to serve as an "conflict target", which @@ -317,8 +326,10 @@ class DoUpdate extends UpsertClause { /// been inserted, use [DoUpdate.withExcluded]. /// /// For an example, see [InsertStatement.insert]. - DoUpdate(Insertable Function(T old) update, {this.target}) + DoUpdate(Insertable Function(T old) update, + {this.target, Expression Function(T old)? where}) : _creator = ((old, _) => update(old)), + _where = where == null ? null : ((old, _) => Where(where(old))), _usesExcludedTable = false; /// Creates a `DO UPDATE` clause. @@ -330,8 +341,12 @@ class DoUpdate extends UpsertClause { /// parameter. /// /// For an example, see [InsertStatement.insert]. - DoUpdate.withExcluded(this._creator, {this.target}) - : _usesExcludedTable = true; + DoUpdate.withExcluded(this._creator, + {this.target, Expression Function(T old, T excluded)? where}) + : _usesExcludedTable = true, + _where = where == null + ? null + : ((old, excluded) => Where(where(old, excluded))); Insertable _createInsertable(TableInfo table) { return _creator(table.asDslTable, table.createAlias('excluded').asDslTable); diff --git a/moor/test/insert_test.dart b/moor/test/insert_test.dart index 2e7fb2ac..89d01748 100644 --- a/moor/test/insert_test.dart +++ b/moor/test/insert_test.dart @@ -205,6 +205,23 @@ void main() { )); }); + test('can use an upsert clause with where', () async { + await db.into(db.todosTable).insert( + TodosTableCompanion.insert(content: 'my content'), + onConflict: DoUpdate((old) { + return TodosTableCompanion.custom( + content: const Variable('important: ') + old.content); + }, where: (old) => old.category.equals(1)), + ); + + verify(executor.runInsert( + 'INSERT INTO todos (content) VALUES (?) ' + 'ON CONFLICT(id) DO UPDATE SET content = ? || content ' + 'WHERE category = ?', + argThat(equals(['my content', 'important: ', 1])), + )); + }); + test( 'can use multiple upsert targets', () async { @@ -236,6 +253,36 @@ void main() { }, ); + test( + 'can use multiple upsert targets with where', + () 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); + }, where: (old) => old.category.equals(1)), + DoUpdate((old) { + return TodosTableCompanion.custom( + content: const Variable('second: ') + old.content); + }, + target: [db.todosTable.content], + where: (old) => old.category.equals(1)), + ])); + + verify(executor.runInsert( + 'INSERT INTO todos (content) VALUES (?) ' + 'ON CONFLICT(id) DO UPDATE SET content = ? || content ' + 'WHERE category = ? ' + 'ON CONFLICT(content) DO UPDATE SET content = ? || content ' + 'WHERE category = ?', + argThat(equals(['my content', 'important: ', 1, 'second: ', 1])), + )); + }, + ); + test('can use a custom conflict clause', () async { await db.into(db.todosTable).insert( TodosTableCompanion.insert(content: 'my content'), @@ -252,6 +299,23 @@ void main() { )); }); + test('can use a custom conflict clause with where', () async { + await db.into(db.todosTable).insert( + TodosTableCompanion.insert(content: 'my content'), + onConflict: DoUpdate( + (old) => TodosTableCompanion.insert(content: 'changed'), + target: [db.todosTable.content], + where: (old) => old.content.equalsExp(old.title)), + ); + + verify(executor.runInsert( + 'INSERT INTO todos (content) VALUES (?) ' + 'ON CONFLICT(content) DO UPDATE SET content = ? ' + 'WHERE content = title', + argThat(equals(['my content', 'changed'])), + )); + }); + test('insertOnConflictUpdate', () async { when(executor.runInsert(any, any)).thenAnswer((_) => Future.value(3)); @@ -284,6 +348,25 @@ void main() { )); }); + test('can access excluded row in upsert with where', () async { + await db.into(db.todosTable).insert( + TodosTableCompanion.insert(content: 'content'), + onConflict: DoUpdate.withExcluded( + (old, excluded) => TodosTableCompanion.custom( + content: old.content + excluded.content, + ), + where: (old, excluded) => old.title.equalsExp(excluded.title)), + ); + + verify(executor.runInsert( + 'INSERT INTO todos (content) VALUES (?) ' + 'ON CONFLICT(id) DO UPDATE ' + 'SET content = todos.content || excluded.content ' + 'WHERE todos.title = excluded.title', + ['content'], + )); + }); + test('applies implicit type converter', () async { await db.into(db.categories).insert(CategoriesCompanion.insert( description: 'description', diff --git a/moor/test/integration_tests/insert_integration_test.dart b/moor/test/integration_tests/insert_integration_test.dart index fa044dc9..e0f604fe 100644 --- a/moor/test/integration_tests/insert_integration_test.dart +++ b/moor/test/integration_tests/insert_integration_test.dart @@ -47,6 +47,56 @@ void main() { expect(row.description, 'original description new description'); }); + test('insert with DoUpdate and excluded row and where statement true', + () async { + await db.into(db.categories).insert( + CategoriesCompanion.insert(description: 'original description')); + + var row = await db.select(db.categories).getSingle(); + + await db.into(db.categories).insert( + CategoriesCompanion( + id: Value(row.id), + priority: const Value(CategoryPriority.medium), + description: const Value('new description'), + ), + onConflict: DoUpdate.withExcluded( + (old, excluded) => CategoriesCompanion.custom( + description: old.description + + const Constant(' ') + + excluded.description), + where: (old, excluded) => + old.priority.isBiggerOrEqual(excluded.priority))); + + row = await db.select(db.categories).getSingle(); + expect(row.description, 'original description'); + }); + + test('insert with DoUpdate and excluded row and where statement false', + () async { + await db.into(db.categories).insert( + CategoriesCompanion.insert(description: 'original description')); + + var row = await db.select(db.categories).getSingle(); + + await db.into(db.categories).insert( + CategoriesCompanion( + id: Value(row.id), + priority: const Value(CategoryPriority.low), + description: const Value('new description'), + ), + onConflict: DoUpdate.withExcluded( + (old, excluded) => CategoriesCompanion.custom( + description: old.description + + const Constant(' ') + + excluded.description), + where: (old, excluded) => + old.priority.isBiggerOrEqual(excluded.priority))); + + row = await db.select(db.categories).getSingle(); + expect(row.description, 'original description new description'); + }); + test('returning', () async { final entry = await db.into(db.categories).insertReturning( CategoriesCompanion.insert(description: 'Description')); From 3f3236cf7bd9c500e41bf7d283d2c8740852be09 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 5 Oct 2021 19:47:17 +0300 Subject: [PATCH 2/4] add doc to DoUpdate where --- .../src/runtime/query_builder/statements/insert.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/moor/lib/src/runtime/query_builder/statements/insert.dart b/moor/lib/src/runtime/query_builder/statements/insert.dart index 70811bb3..70f2dacb 100644 --- a/moor/lib/src/runtime/query_builder/statements/insert.dart +++ b/moor/lib/src/runtime/query_builder/statements/insert.dart @@ -325,6 +325,11 @@ class DoUpdate extends UpsertClause { /// If you need to refer to both the old row and the row that would have /// been inserted, use [DoUpdate.withExcluded]. /// + /// The optional [where] clause can be used to + /// disable the update based on the old value. + /// If a [where] clause is set and it evaluates to false, + /// a conflict will keep the old row without applying the update + /// /// For an example, see [InsertStatement.insert]. DoUpdate(Insertable Function(T old) update, {this.target, Expression Function(T old)? where}) @@ -339,7 +344,12 @@ class DoUpdate extends UpsertClause { /// It can refer to the values from the old row in the first parameter and /// to columns in the row that couldn't be inserted with the `excluded` /// parameter. - /// + /// + /// The optional [where] clause can be used to + /// disable the update based on the old value. + /// If a [where] clause is set and it evaluates to false, + /// a conflict will keep the old row without applying the update + /// /// For an example, see [InsertStatement.insert]. DoUpdate.withExcluded(this._creator, {this.target, Expression Function(T old, T excluded)? where}) From 849754332f813784f01391dcd9a50ef95e8d7ebc Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 5 Oct 2021 19:48:35 +0300 Subject: [PATCH 3/4] update changelog --- moor/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/moor/CHANGELOG.md b/moor/CHANGELOG.md index a8297c82..c0222393 100644 --- a/moor/CHANGELOG.md +++ b/moor/CHANGELOG.md @@ -1,6 +1,7 @@ ## 4.6.0-dev - Add `DoUpdate.withExcluded` to refer to the excluded row in an upsert clause. +- Add optional `where` clause to `DoUpdate` constructors ## 4.5.0 From 639e99ba4a3521474be082a41d4fd0dfc50e2d83 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 5 Oct 2021 20:14:16 +0200 Subject: [PATCH 4/4] Formatting --- .../query_builder/statements/insert.dart | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/moor/lib/src/runtime/query_builder/statements/insert.dart b/moor/lib/src/runtime/query_builder/statements/insert.dart index 70f2dacb..947ad253 100644 --- a/moor/lib/src/runtime/query_builder/statements/insert.dart +++ b/moor/lib/src/runtime/query_builder/statements/insert.dart @@ -325,10 +325,9 @@ class DoUpdate extends UpsertClause { /// If you need to refer to both the old row and the row that would have /// been inserted, use [DoUpdate.withExcluded]. /// - /// The optional [where] clause can be used to - /// disable the update based on the old value. - /// If a [where] clause is set and it evaluates to false, - /// a conflict will keep the old row without applying the update + /// The optional [where] clause can be used to disable the update based on + /// the old value. If a [where] clause is set and it evaluates to false, a + /// conflict will keep the old row without applying the update. /// /// For an example, see [InsertStatement.insert]. DoUpdate(Insertable Function(T old) update, @@ -344,12 +343,11 @@ class DoUpdate extends UpsertClause { /// It can refer to the values from the old row in the first parameter and /// to columns in the row that couldn't be inserted with the `excluded` /// parameter. - /// - /// The optional [where] clause can be used to - /// disable the update based on the old value. - /// If a [where] clause is set and it evaluates to false, - /// a conflict will keep the old row without applying the update - /// + /// + /// The optional [where] clause can be used to disable the update based on + /// the old value. If a [where] clause is set and it evaluates to false, a + /// conflict will keep the old row without applying the update. + /// /// For an example, see [InsertStatement.insert]. DoUpdate.withExcluded(this._creator, {this.target, Expression Function(T old, T excluded)? where})