mirror of https://github.com/AMT-Cheif/drift.git
Start documenting moor 2.0 features
This commit is contained in:
parent
a38e883282
commit
448ff10823
|
@ -56,6 +56,20 @@ Future<List<TodoEntry>> sortEntriesAlphabetically() {
|
||||||
```
|
```
|
||||||
You can also reverse the order by setting the `mode` property of the `OrderingTerm` to
|
You can also reverse the order by setting the `mode` property of the `OrderingTerm` to
|
||||||
`OrderingMode.desc`.
|
`OrderingMode.desc`.
|
||||||
|
|
||||||
|
### Single values
|
||||||
|
If you now a query is never going to return more than one row, wrapping the result in a `List`
|
||||||
|
can be tedious. Moor lets you work around that with `getSingle` and `watchSingle`:
|
||||||
|
```dart
|
||||||
|
Stream<TodoEntry> entryById(int id) {
|
||||||
|
return (select(todos)..where((t) => t.id.equals(id))).watchSingle();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
If an entry with the provided id exists, it will be sent to the stream. Otherwise,
|
||||||
|
`null` will be added to stream. If a query used with `watchSingle` ever returns
|
||||||
|
more than one entry (which is impossible in this case), an error will be added
|
||||||
|
instead.
|
||||||
|
|
||||||
## Updates and deletes
|
## Updates and deletes
|
||||||
You can use the generated classes to update individual fields of any row:
|
You can use the generated classes to update individual fields of any row:
|
||||||
```dart
|
```dart
|
||||||
|
@ -126,3 +140,6 @@ addTodoEntry(
|
||||||
If a column is nullable or has a default value (this includes auto-increments), the field
|
If a column is nullable or has a default value (this includes auto-increments), the field
|
||||||
can be omitted. All other fields must be set and non-null. The `insert` method will throw
|
can be omitted. All other fields must be set and non-null. The `insert` method will throw
|
||||||
otherwise.
|
otherwise.
|
||||||
|
|
||||||
|
Multiple inserts can be batched by using `insertAll` - it takes a list of companions instead
|
||||||
|
of a single companion.
|
|
@ -1,11 +1,18 @@
|
||||||
---
|
---
|
||||||
title: "Custom queries"
|
title: "(Legacy) Custom queries"
|
||||||
weight: 10
|
weight: 10
|
||||||
description: Let moor generate Dart from your SQL statements
|
description: Let moor generate Dart from your SQL statements
|
||||||
aliases:
|
aliases:
|
||||||
- /queries/custom
|
- /queries/custom
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{{% alert title="Outdated feature" color="warning" %}}
|
||||||
|
With moor 2.0, we moved the new `.moor` files out of preview and added some powerful features to them.
|
||||||
|
They are easier to use than the approaches described here. While these features will continue to
|
||||||
|
be supported, moor files will get better tooling support in the future and we recommend to
|
||||||
|
migrate. See [their api]({{%relref "moor_files.md"%}}) for details.
|
||||||
|
{{% /alert %}}
|
||||||
|
|
||||||
Altough moor includes a fluent api that can be used to model most statements, advanced
|
Altough moor includes a fluent api that can be used to model most statements, advanced
|
||||||
features like `GROUP BY` statements or window functions are not yet supported. You can
|
features like `GROUP BY` statements or window functions are not yet supported. You can
|
||||||
use these features with custom statements. You don't have to miss out on other benefits
|
use these features with custom statements. You don't have to miss out on other benefits
|
||||||
|
@ -44,7 +51,7 @@ To use this feature, it's helpful to know how Dart tables are named in sql. For
|
||||||
override `tableName`, the name in sql will be the `snake_case` of the class name. So a Dart table
|
override `tableName`, the name in sql will be the `snake_case` of the class name. So a Dart table
|
||||||
called `Categories` will be named `categories`, a table called `UserAddressInformation` would be
|
called `Categories` will be named `categories`, a table called `UserAddressInformation` would be
|
||||||
called `user_address_information`. The same rule applies to column getters without an explicit name.
|
called `user_address_information`. The same rule applies to column getters without an explicit name.
|
||||||
Tables and columns declared in [SQL tables]({{< relref "custom_tables.md" >}}) will always have the
|
Tables and columns declared in [Moor files]({{< relref "moor_files.md" >}}) will always have the
|
||||||
name you specified.
|
name you specified.
|
||||||
{{% /alert %}}
|
{{% /alert %}}
|
||||||
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
---
|
|
||||||
title: "Tables from SQL"
|
|
||||||
weight: 20
|
|
||||||
description: Generate tables from `CREATE TABLE` statements.
|
|
||||||
---
|
|
||||||
|
|
||||||
{{% alert title="Experimental feature" %}}
|
|
||||||
At the moment, creating table classes from `CREATE TABLE` statements is an experimental feature.
|
|
||||||
If you run into any issues, please create an issue and let us know, thanks!
|
|
||||||
{{% /alert %}}
|
|
||||||
|
|
||||||
With moor, you can specify your table classes in Dart and it will generate matching
|
|
||||||
`CREATE TABLE` statements for you. But if you prefer to write `CREATE TABLE` statements and have
|
|
||||||
moor generating fitting Dart classes, that works too.
|
|
||||||
|
|
||||||
To use this feature, create a (or multiple) `.moor` file somewhere in your project. You can fill
|
|
||||||
them with create table statements:
|
|
||||||
```sql
|
|
||||||
CREATE TABLE states (
|
|
||||||
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE experiments (
|
|
||||||
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
description TEXT NOT NULL,
|
|
||||||
state INT REFERENCES states(id) ON UPDATE CASCADE ON DELETE SET NULL
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, import these tables to your database with:
|
|
||||||
```dart
|
|
||||||
@UseMoor(include: {'experiments.moor'})
|
|
||||||
class ExperimentsDb extends _$ExperimentsDb {
|
|
||||||
```
|
|
||||||
|
|
||||||
All the tables will then be available inside your database class, just like they
|
|
||||||
would be if you wrote them in Dart. If you want to use this feature on an DAO,
|
|
||||||
you'll also need to `include` the .moor file on that class. Moor supports both
|
|
||||||
relative imports (like above) and absolute imports (like `package:your_app/src/tables/experiments.moor`)
|
|
||||||
Of course, this feature works perfectly together with features like generated
|
|
||||||
custom queries and query-streams.
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
---
|
||||||
|
title: "Moor files"
|
||||||
|
weight: 1
|
||||||
|
description: Learn everything about the new `.moor` files which can contain tables and queries
|
||||||
|
|
||||||
|
aliases:
|
||||||
|
- /docs/using-sql/custom_tables/ # Redirect from outdated "custom tables" page which has been deleted
|
||||||
|
---
|
||||||
|
|
||||||
|
Moor files are a new feature that lets you write all your database code in SQL - moor will generate typesafe APIs for them.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
To use this feature, lets create two files: `database.dart` and `tables.moor`. The Dart file is pretty straightforward:
|
||||||
|
```dart
|
||||||
|
import 'package:moor/moor.dart';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
@UseMoor(
|
||||||
|
include: {'tables.moor'},
|
||||||
|
)
|
||||||
|
class MoorDb extends _$MoorDb {
|
||||||
|
MoorDb() : super(FlutterQueryExecutor.inDatabaseFolder('app.db'));
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can now declare tables and queries in the moor file:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE todos (
|
||||||
|
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
category INTEGER REFERENCES categories(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE categories (
|
||||||
|
id INT NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
description TEXT NOT NULL
|
||||||
|
) AS Category; -- the AS xyz after the table defines the data class name
|
||||||
|
|
||||||
|
-- we can put named sql queries in here as well:
|
||||||
|
createEntry: INSERT INTO todos (title, content) VALUES (:title, :content);
|
||||||
|
deleteById: DELETE FROM todos WHERE id = :id;
|
||||||
|
watchAllTodos: SELECT * FROM todos;
|
||||||
|
```
|
||||||
|
|
||||||
|
After running the build runner, moor will write the `database.g.dart`
|
||||||
|
file which contains the `_$MoorDb` superclass. Let's take a look at
|
||||||
|
what we got:
|
||||||
|
|
||||||
|
- Generated data classes (`Todo` and `Category`), and companion versions
|
||||||
|
for inserts (see [Dart Interop](#dart-interop) for info). By default,
|
||||||
|
we strip a trailing "s" from the table name for the class. That's why
|
||||||
|
we used `AS Category` on the second table - it would have been called
|
||||||
|
`Categorie` otherwise.
|
||||||
|
- Methods to run the queries:
|
||||||
|
- a `Future<int> createEntry(String title, String content)` method. It
|
||||||
|
creates a new todo entry with the provided data and returns the id of
|
||||||
|
the entry created.
|
||||||
|
- `Future<int> deleteById(int id)`: Deletes a todo entry by its id, and
|
||||||
|
returns the amount of rows affected.
|
||||||
|
- `Selectable<AllTodosResult> allTodos()`. It can be used to get, or
|
||||||
|
watch, all todo entries. It can be used with `allTodos().get()` and
|
||||||
|
`allTodos().watch()`.
|
||||||
|
- Classes for select statements that don't match a table. In the example
|
||||||
|
above, thats the `AllTodosResult` class, which contains all fields from
|
||||||
|
`todos` and the description of the associated category.
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
We support regular variables (`?`), explictly indexed variables (`?123`)
|
||||||
|
and colon-named variables (`:id`). We don't support variables declared
|
||||||
|
with @ or $. The compiler will attempt to infer the variable's type by
|
||||||
|
looking at its context. This lets moor generate typesafe apis for your
|
||||||
|
queries, the variables will be written as parameters to your method.
|
||||||
|
|
||||||
|
### Arrays
|
||||||
|
If you want to check whether a value is in an array of values, you can
|
||||||
|
use `IN ?`. That's not valid sql, but moor will desugar that at runtime. So, for this query:
|
||||||
|
```sql
|
||||||
|
entriesWithId: SELECT * FROM todos WHERE id IN ?;
|
||||||
|
```
|
||||||
|
Moor will generate a `Selectable<Todo> entriesWithId(List<int> ids)`
|
||||||
|
method (`entriesWithId([1,2])` would run `SELECT * ... id IN (?1, ?2)`
|
||||||
|
and bind the arguments accordingly). To support this, we only have two
|
||||||
|
restrictions:
|
||||||
|
|
||||||
|
1. __No explicit variables__: Running `WHERE id IN ?2` will be rejected
|
||||||
|
at build time. As the variable is expanded, giving it a single index is
|
||||||
|
invalid.
|
||||||
|
2. __No higher explicit index after a variable__: Running
|
||||||
|
`WHERE id IN ? OR title = ?2` will also be rejected. Expanding the
|
||||||
|
variable can clash with the explicit index, which is why moor forbids
|
||||||
|
it. Of course, `id IN ? OR title = ?` will work as expected.
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
{{% alert title="Limited support" %}}
|
||||||
|
> Importing a moor file from another moor file will work as expected.
|
||||||
|
Unfortunately, importing a Dart file from moor does not work in all
|
||||||
|
scenarios. Please upvote [this issue](https://github.com/dart-lang/build/issues/493)
|
||||||
|
on the build package to help solve this.
|
||||||
|
{{% /alert %}}
|
||||||
|
|
||||||
|
You can put import statements at the top of a `moor` file:
|
||||||
|
```sql
|
||||||
|
import 'other.moor'; -- single quotes are required for imports
|
||||||
|
```
|
||||||
|
All tables reachable from the other file will then also be visible in
|
||||||
|
the current file and to the database that `includes` it. Importing
|
||||||
|
Dart files into a moor file will also work - then, all the tables
|
||||||
|
declared via Dart tables can be used inside queries.
|
||||||
|
|
||||||
|
## Dart interop
|
||||||
|
Moor files work perfectly together with moor's existing Dart API:
|
||||||
|
|
||||||
|
- you can write Dart queries for moor files:
|
||||||
|
```dart
|
||||||
|
Future<void> insert(TodosCompanion companion) async {
|
||||||
|
await into(todos).insert(companion);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- by importing Dart files into a moor file, you can write sql queries for
|
||||||
|
tables declared in Dart.
|
||||||
|
- generated methods for queries can be used in transactions, they work
|
||||||
|
together with auto-updating queries, etc.
|
||||||
|
|
||||||
|
You can make most of both SQL and Dart by "Dart Templates", which is a
|
||||||
|
Dart expression that gets inlined to a query. To use them, declare a
|
||||||
|
$-variable in a query:
|
||||||
|
```sql
|
||||||
|
_filterTodos: SELECT * FROM todos WHERE $predicate;
|
||||||
|
```
|
||||||
|
Moor will generate a `Selectable<Todo> _filterTodos(Expression<bool, BoolType> predicate)` method which can be used to construct dynamic
|
||||||
|
filters at runtime:
|
||||||
|
```dart
|
||||||
|
Stream<List<Todo>> watchInCategory(int category) {
|
||||||
|
return _filterTodos(todos.category.equals(category)).watch();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This feature works for
|
||||||
|
|
||||||
|
- expressions
|
||||||
|
- single ordering terms: `SELECT * FROM todos ORDER BY $term, id ASC`
|
||||||
|
will generate a method taking an `OrderingTerm`.
|
||||||
|
- whole order-by clauses: `SELECT * FROM todos ORDER BY $order`
|
||||||
|
- limit clauses: `SELECT * FROM todos LIMIT $limit`
|
|
@ -16,7 +16,7 @@ following example, which deals with deleting a category, we move all todo entrie
|
||||||
in that category back to the default category:
|
in that category back to the default category:
|
||||||
```dart
|
```dart
|
||||||
Future deleteCategory(Category category) {
|
Future deleteCategory(Category category) {
|
||||||
return transaction((_) async {
|
return transaction(() async {
|
||||||
// first, move the affected todo entries back to the default category
|
// first, move the affected todo entries back to the default category
|
||||||
await customUpdate(
|
await customUpdate(
|
||||||
'UPDATE todos SET category = NULL WHERE category = ?',
|
'UPDATE todos SET category = NULL WHERE category = ?',
|
||||||
|
@ -30,22 +30,9 @@ Future deleteCategory(Category category) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
{{% alert title="About that _" color="info" %}}
|
|
||||||
You might have noticed that `_` parameter on the `transaction()` callback. That parameter would
|
|
||||||
be a special version of the database that runs all the methods called on it inside the transaction.
|
|
||||||
In previous moor versions, it was important to call everything on that parameter, e.g.
|
|
||||||
```dart
|
|
||||||
transaction((db) async {
|
|
||||||
await db.delete(categories).delete(category);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
Starting from moor 1.6, this is no longer neccessary, we can figure out that you meant to run that
|
|
||||||
in a transaction because it was called from inside a `transaction` callback. We're going to remove
|
|
||||||
that parameter entirely in moor 2.0.
|
|
||||||
{{% /alert %}}
|
|
||||||
|
|
||||||
## ⚠️ Gotchas
|
## ⚠️ Gotchas
|
||||||
There are a couple of things that should be kept in mind when working with transactions:
|
There are a couple of things that should be kept in mind when working with transactions:
|
||||||
|
|
||||||
1. __Await all calls__: All queries inside the transaction must be `await`-ed. The transaction
|
1. __Await all calls__: All queries inside the transaction must be `await`-ed. The transaction
|
||||||
will complete when the inner method completes. Without `await`, some queries might be operating
|
will complete when the inner method completes. Without `await`, some queries might be operating
|
||||||
on the transaction after it has been closed! This can cause data loss or runtime crashes.
|
on the transaction after it has been closed! This can cause data loss or runtime crashes.
|
||||||
|
|
Loading…
Reference in New Issue