From 492d35c3168d59a3ec78809424ec9deeed44192f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 28 Nov 2024 00:09:57 +0100 Subject: [PATCH] Document custom companions better --- docs/docs/dart_api/rows.md | 10 +++++++- docs/docs/dart_api/writes.md | 31 +++++++++++++++-------- docs/docs/index.md | 4 +-- docs/lib/snippets/dart_api/dataclass.dart | 6 +++++ 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/docs/dart_api/rows.md b/docs/docs/dart_api/rows.md index 4ccd84e64..83887711c 100644 --- a/docs/docs/dart_api/rows.md +++ b/docs/docs/dart_api/rows.md @@ -130,7 +130,6 @@ This class is introduced for two reasons: To solve this problem, companions represent partial rows by using Drift's `Value` class. `Value`s store a value (which can be nullable) or explicitly indicate that a value is _absent_: - {{ load_snippet('generated-value','lib/snippets/dart_api/dataclass.dart.excerpt.json') }} 1. Since the `id` is `autoIncrement()`, the database will pick a value for us and no value @@ -139,6 +138,15 @@ To solve this problem, companions represent partial rows by using Drift's `Value on companions that avoids `Value` wrappers where they are not required. This insert could be written as `UsersCompanion.insert(username: 'user')` +### Updating with SQL expressions + +Companions also provide a `.custom` method used when mixing values and SQL expressions for +updates or deletes. +For instance, this update statement changes all the names of all rows in the `users` table +to be lower-case: + +{{ load_snippet('companion-custom','lib/snippets/dart_api/dataclass.dart.excerpt.json') }} + ## Custom dataclass The generated dataclass works well for most cases, but you might want to use your own class to diff --git a/docs/docs/dart_api/writes.md b/docs/docs/dart_api/writes.md index f37312f53..a5fc2901e 100644 --- a/docs/docs/dart_api/writes.md +++ b/docs/docs/dart_api/writes.md @@ -8,6 +8,7 @@ description: Select rows or invidiual columns from tables in Dart ## Updates and deletes You can use the generated classes to update individual fields of any row: + ```dart Future moveImportantTasksIntoCategory(Category target) { // for updates, we use the "companion" version of a generated class. This wraps the @@ -36,12 +37,12 @@ Future feelingLazy() { return (delete(todos)..where((t) => t.id.isSmallerThanValue(10))).go(); } ``` -__⚠️ Caution:__ If you don't explicitly add a `where` clause on updates or deletes, +__⚠️ Caution:__ If you don't explicitly add a `where` clause on updates or deletes, the statement will affect all rows in the table! !!! note "Entries, companions - why do we need all of this?" - + You might have noticed that we used a `TodosCompanion` for the first update instead of just passing a `Todo`. Drift generates the `Todo` class (also called _data class_ for the table) to hold a __full__ row with all its data. For _partial_ data, @@ -50,17 +51,27 @@ the statement will affect all rows in the table! Why is that necessary? If a field was set to `null`, we wouldn't know whether we need to set that column back to null in the database or if we should just leave it unchanged. Fields in the companions have a special `Value.absent()` state which makes this explicit. - + Companions also have a special constructor for inserts - all columns which don't have a default value and aren't nullable are marked `@required` on that constructor. This makes companions easier to use for inserts because you know which fields to set. - +### Updating with SQL expressions +In some cases, you might want to update many rows based on their current value. +One option would be to first select the affected rows into Dart objects, create companions +based on those results and use them for updates. +If the update can be described in SQL, a more efficient way is available with `Companion.custom`: + +{{ load_snippet('companion-custom','lib/snippets/dart_api/dataclass.dart.excerpt.json') }} + +Here, the `name` column in the `users` table is changed to lowercase for all existing rows. +Since `.lower()` on columns is implemented in the database, the rows don't have to be loaded +into Dart during the statement. ## Inserts You can very easily insert any valid object into tables. As some values can be absent -(like default values that we don't have to set explicitly), we again use the +(like default values that we don't have to set explicitly), we again use the companion version. ```dart // returns the generated id @@ -112,8 +123,6 @@ This makes them suitable for bulk insert or update operations. ### Upserts - - Upserts are a feature from newer sqlite3 versions that allows an insert to behave like an update if a conflicting row already exists. @@ -151,17 +160,17 @@ counter if it already exists: !!! note "Unique constraints and conflict targets" - + Both `insertOnConflictUpdate` and `onConflict: DoUpdate` use an `DO UPDATE` upsert in sql. This requires us to provide a so-called "conflict target", a set of columns to check for uniqueness violations. By default, drift will use the table's primary key as conflict target. That works in most cases, but if you have custom `UNIQUE` constraints on some columns, you'll need to use the `target` parameter on `DoUpdate` in Dart to include those columns: - + {{ load_snippet('upsert-target','lib/snippets/modular/upserts.dart.excerpt.json') }} - - + + diff --git a/docs/docs/index.md b/docs/docs/index.md index 425b855c2..00e073bbf 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -8,8 +8,8 @@ description: Welcome to drift's documentation. This site shows you what drift ca ## Welcome to drift Drift is a reactive persistence library for Dart and Flutter applications. It's built on top -of database libraries like [the sqlite3 package](https://pub.dev/packages/sqlite3), [sqflite](https://pub.dev/packages/sqflite) or [sql.js](https://github.com/sql-js/sql.js/) -and provides additional features, like: +of database libraries like [the sqlite3 package](https://pub.dev/packages/sqlite3), [sqflite](https://pub.dev/packages/sqflite) and others. +Adding to these libraries, drift provides additional features, like: - __Type safety__: Instead of writing SQL queries manually and parsing the `List>` that they return, drift turns rows into objects of your choice. diff --git a/docs/lib/snippets/dart_api/dataclass.dart b/docs/lib/snippets/dart_api/dataclass.dart index 856fbf033..6ed746db4 100644 --- a/docs/lib/snippets/dart_api/dataclass.dart +++ b/docs/lib/snippets/dart_api/dataclass.dart @@ -69,6 +69,12 @@ void _queryManager(Database db) async { await (db.update(db.users)..where((tbl) => tbl.id.equals(1))) .write(UsersCompanion(username: Value("Updated name"))); // #enddocregion generated-value + + // #docregion companion-custom + await db + .update(db.users) + .write(UsersCompanion.custom(username: db.users.username.lower())); + // #enddocregion companion-custom } void _queryCore(Database db) async {