diff --git a/docs/lib/snippets/drift_files/custom_queries.dart b/docs/lib/snippets/drift_files/custom_queries.dart new file mode 100644 index 00000000..ddf592a1 --- /dev/null +++ b/docs/lib/snippets/drift_files/custom_queries.dart @@ -0,0 +1,74 @@ +import 'package:drift/drift.dart'; + +import '../tables/filename.dart'; + +part 'custom_queries.g.dart'; + +// #docregion manual +class CategoryWithCount { + final Category category; + final int count; // amount of entries in this category + + CategoryWithCount({required this.category, required this.count}); +} + +// #enddocregion manual + +// #docregion setup +@DriftDatabase( + tables: [Todos, Categories], + queries: { + 'categoriesWithCount': 'SELECT *, ' + '(SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" ' + 'FROM categories c;' + }, +) +class MyDatabase extends _$MyDatabase { + // rest of class stays the same + // #enddocregion setup + @override + int get schemaVersion => 1; + + MyDatabase(QueryExecutor e) : super(e); + + // #docregion run + Future useGeneratedQuery() async { + // The generated query can be run once as a future: + await categoriesWithCount().get(); + + // Or multiple times as a stream + await for (final snapshot in categoriesWithCount().watch()) { + print('Found ${snapshot.length} category results'); + } + } + + // #enddocregion run + // #docregion manual + // then, in the database class: + Stream> allCategoriesWithCount() { + // select all categories and load how many associated entries there are for + // each category + return customSelect( + 'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount"' + ' FROM categories c;', + // used for the stream: the stream will update when either table changes + readsFrom: {todos, categories}, + ).watch().map((rows) { + // we get list of rows here. We just have to turn the raw data from the + // row into a CategoryWithCount instnace. As we defined the Category table + // earlier, drift knows how to parse a category. The only thing left to do + // manually is extracting the amount. + return rows + .map((row) => CategoryWithCount( + category: Category.fromData(row.data), + count: row.read('amount'), + )) + .toList(); + }); + } + +// #enddocregion manual + + // #docregion setup +} +// #enddocregion setup diff --git a/docs/pages/docs/Using SQL/custom_queries.md b/docs/pages/docs/Using SQL/custom_queries.md index 8002bf51..caa38e5d 100644 --- a/docs/pages/docs/Using SQL/custom_queries.md +++ b/docs/pages/docs/Using SQL/custom_queries.md @@ -2,45 +2,56 @@ data: title: "Custom queries" weight: 10 - description: Let drift generate Dart from your SQL statements + description: Write SQL for advanced queries that drift can't express in Dart yet. aliases: - /queries/custom template: layouts/docs/single --- +{% assign snippets = "package:drift_docs/snippets/drift_files/custom_queries.dart.excerpt.json" | readString | json_decode %} + Although drift includes a fluent api that can be used to model most statements, advanced -features like `WITH` clauses or subqueries aren't supported yet. You can -use these features with custom statements. You don't have to miss out on other benefits -drift brings, though: Drift helps you parse the result rows and custom queries also -support auto-updating streams. +features like `WITH` clauses or some subqueries aren't supported yet. +However, you can use methods like `customSelect` and `customStatement` to run advanced +statements on the database by writing the SQL manually. + +For most custom queries, drift can analyze their SQL at compile time, make sure they're valid +and generate a type-safe API for them. +This approach can be much safer than writing custom SQL at runtime. + +This page describes both approaches: The first section introduces methods generated by drift, +the second section gives an example for a custom query defined at runtime. ## Statements with a generated api You can instruct drift to automatically generate a typesafe API for your select, update and delete statements. Of course, you can still write custom - sql manually. See the sections below for details. +sql manually. See the sections below for details. To use this feature, all you need to is define your queries in your `DriftDatabase` annotation: -```dart -@DriftDatabase( - tables: [Todos, Categories], - queries: { - 'categoriesWithCount': - 'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;' - }, -) -class MyDatabase extends _$MyDatabase { - // rest of class stays the same -} -``` -After running the build step again, drift will have written the `CategoriesWithCountResult` class for you - -it will hold the result of your query. Also, the `_$MyDatabase` class from which you inherit will have the -methods `categoriesWithCount` (which runs the query once) and `watchCategoriesWithCount` (which returns -an auto-updating stream). -Queries can have parameters in them by using the `?` or `:name` syntax. When your queries contains parameters, -drift will figure out an appropriate type for them and include them in the generated methods. For instance, +{% include "blocks/snippet" snippets = snippets name = "setup" %} + +After running the build step again, drift will have written the `CategoriesWithCountResult` class for you - +it will hold the result of your query. Also, the `_$MyDatabase` class from which you inherit will have a +`Selectable categoriesWithCount()` method which can be used to run the query. +Like all `Selectable`s in drift, you can use `get()` to run the query once or `watch()` to get an auto-updating +stream of results: + +{% block "blocks/alert" title="Better support for custom queries in drift files" %} +Defining SQL in the `@DriftDatabase` annotation is a great way to define a few custom queries. For apps that +use lots of custom queries, extracting them into separate files may be more manageable. +[Drift files]({{ "drift_files.md" | pageUrl }}), which can be included into the database, are a really great fit for this, and may be easier +to use. +{% endblock %} + +{% include "blocks/snippet" snippets = snippets name = "run" %} + +Queries can have parameters in them by using the `?` or `:name` syntax. For parameters in queries, +drift will figure out an appropriate type and include them in the generated methods. For instance, `'categoryById': 'SELECT * FROM categories WHERE id = :id'` will generate the method `categoryById(int id)`. +Drift also supports additional convenience features in custom queries, like embededding Dart expressions in +SQL. For more details, see the documentation on [drift files]({{ 'drift_files.md' | pageUrl }}). {% block "blocks/alert" title="On table names" color="info" %} To use this feature, it's helpful to know how Dart tables are named in sql. For tables that don't @@ -63,31 +74,9 @@ still send custom queries by calling `customSelect` for a one-time query or the underlying data changes. Using the todo example introduced in the [getting started guide]({{ "../Getting started/index.md" | pageUrl }}), we can write this query which will load the amount of todo entries in each category: -```dart -class CategoryWithCount { - final Category category; - final int count; // amount of entries in this category - CategoryWithCount(this.category, this.count); -} +{% include "blocks/snippet" snippets = snippets name = "manual" %} -// then, in the database class: -Stream> categoriesWithCount() { - // select all categories and load how many associated entries there are for - // each category - return customSelect( - 'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;', - readsFrom: {todos, categories}, // used for the stream: the stream will update when either table changes - ).watch().map((rows) { - // we get list of rows here. We just have to turn the raw data from the row into a - // CategoryWithCount. As we defined the Category table earlier, drift knows how to parse - // a category. The only thing left to do manually is extracting the amount - return rows - .map((row) => CategoryWithCount(Category.fromData(row.data, this), row.read('amount'))) - .toList(); - }); - } -``` For custom selects, you should use the `readsFrom` parameter to specify from which tables the query is reading. When using a `Stream`, drift will be able to know after which updates the stream should emit items.