Skip to content

Commit

Permalink
Start documenting moor 2.0 features
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Sep 20, 2019
1 parent a38e883 commit 448ff10
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 60 deletions.
19 changes: 18 additions & 1 deletion docs/content/en/docs/Getting started/writing_queries.md
Expand Up @@ -56,6 +56,20 @@ Future<List<TodoEntry>> sortEntriesAlphabetically() {
```
You can also reverse the order by setting the `mode` property of the `OrderingTerm` to
`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
You can use the generated classes to update individual fields of any row:
```dart
Expand Down Expand Up @@ -125,4 +139,7 @@ addTodoEntry(
```
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
otherwise.
otherwise.

Multiple inserts can be batched by using `insertAll` - it takes a list of companions instead
of a single companion.
11 changes: 9 additions & 2 deletions docs/content/en/docs/Using SQL/custom_queries.md
@@ -1,11 +1,18 @@
---
title: "Custom queries"
title: "(Legacy) Custom queries"
weight: 10
description: Let moor generate Dart from your SQL statements
aliases:
- /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
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
Expand Down Expand Up @@ -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
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.
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.
{{% /alert %}}

Expand Down
42 changes: 0 additions & 42 deletions docs/content/en/docs/Using SQL/custom_tables.md

This file was deleted.

148 changes: 148 additions & 0 deletions docs/content/en/docs/Using SQL/moor_files.md
@@ -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`
17 changes: 2 additions & 15 deletions docs/content/en/docs/transactions.md
Expand Up @@ -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:
```dart
Future deleteCategory(Category category) {
return transaction((_) async {
return transaction(() async {
// first, move the affected todo entries back to the default category
await customUpdate(
'UPDATE todos SET category = NULL WHERE category = ?',
Expand All @@ -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
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
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.
Expand Down

0 comments on commit 448ff10

Please sign in to comment.