From 955f234cd609519e9af4bfe9281e123c7142adb5 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 30 Nov 2024 22:04:44 +0100 Subject: [PATCH] Fix insert from select with upsert Closes https://github.com/simolus3/drift/issues/3362 --- drift/CHANGELOG.md | 4 ++++ .../query_builder/statements/insert.dart | 8 +++++-- .../test/database/statements/insert_test.dart | 4 ++-- .../insert_integration_test.dart | 24 +++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index f5a66844b..22eccba4b 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.22.1 + +- Fix generated SQL for `insertFromSelect` statements with upserts. + ## 2.22.0 - Add `sqliteAny()` method to tables to declare `ANY` columns. diff --git a/drift/lib/src/runtime/query_builder/statements/insert.dart b/drift/lib/src/runtime/query_builder/statements/insert.dart index d2fd16534..f9b7d1ae4 100644 --- a/drift/lib/src/runtime/query_builder/statements/insert.dart +++ b/drift/lib/src/runtime/query_builder/statements/insert.dart @@ -89,7 +89,6 @@ class InsertStatement { /// target column, and values are expressions added to the select statement. /// /// For an example, see the [documentation website](https://drift.simonbinder.eu/docs/advanced-features/joins/#using-selects-as-insert) - @experimental Future insertFromSelect( BaseSelectStatement select, { required Map columns, @@ -129,7 +128,12 @@ class InsertStatement { ..write( columnNameToSelectColumnName.values.map(ctx.identifier).join(', ')) ..write(' FROM $sourceCte'); - _writeOnConflict(ctx, mode, null, onConflict); + if (onConflict != null) { + // Resolve parsing ambiguity (a `ON` from the conflict clause could also + // be parsed as a join). + ctx.buffer.write(' WHERE TRUE'); + _writeOnConflict(ctx, mode, null, onConflict); + } return await database.withCurrentExecutor((e) async { await e.runInsert(ctx.sql, ctx.boundVariables); diff --git a/drift/test/database/statements/insert_test.dart b/drift/test/database/statements/insert_test.dart index cb21222b4..fe7ebd780 100644 --- a/drift/test/database/statements/insert_test.dart +++ b/drift/test/database/statements/insert_test.dart @@ -605,8 +605,8 @@ void main() { verify(executor.runInsert( 'WITH _source AS (SELECT * FROM "categories") INSERT INTO "categories" ' - '("desc", "priority") SELECT "desc", "priority" FROM _source ' - 'ON CONFLICT("id") DO UPDATE SET "desc" = "desc"', + '("desc", "priority") SELECT "desc", "priority" FROM _source WHERE TRUE' + ' ON CONFLICT("id") DO UPDATE SET "desc" = "desc"', argThat(isEmpty), )); }); diff --git a/drift/test/integration_tests/insert_integration_test.dart b/drift/test/integration_tests/insert_integration_test.dart index d6ec1372e..1575eca6e 100644 --- a/drift/test/integration_tests/insert_integration_test.dart +++ b/drift/test/integration_tests/insert_integration_test.dart @@ -284,5 +284,29 @@ void main() { expect(categeories.map((e) => e.description), ['without entry', 'with entry', 'without entry0', 'with entry1']); }); + + test('upsert', () async { + final originalCategory = await db.categories + .insertReturning(CategoriesCompanion.insert(description: 'original')); + + await db.into(db.categories).insertFromSelect( + db.categories.select(), + columns: { + db.categories.id: db.categories.id, + db.categories.description: db.categories.description, + }, + onConflict: DoUpdate( + (row) => CategoriesCompanion( + description: Value('updated'), + ), + ), + ); + + final category = await db.categories.all().get(); + expect(category, [ + originalCategory.copyWith( + description: 'updated', descriptionInUpperCase: 'UPDATED') + ]); + }); }); }