Update docs on custom SQL

This commit is contained in:
Simon Binder 2022-07-16 11:51:21 +02:00
parent 8cc318f258
commit ef3ee418d4
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
2 changed files with 110 additions and 47 deletions

View File

@ -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<void> 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<List<CategoryWithCount>> 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<int>('amount'),
))
.toList();
});
}
// #enddocregion manual
// #docregion setup
}
// #enddocregion setup

View File

@ -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<CategoriesWithCountResult> 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<List<CategoryWithCount>> 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<int>('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.