Skip to content

Commit

Permalink
ok
Browse files Browse the repository at this point in the history
  • Loading branch information
dickermoshe committed Oct 21, 2024
1 parent 7fb21ba commit 9f5c62d
Show file tree
Hide file tree
Showing 6 changed files with 672 additions and 382 deletions.
13 changes: 9 additions & 4 deletions docs/builders/src/excerpt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,15 @@ class _Excerpt {
final file = span.file;

// First line, cut of `start column - stripIndent` chars at the start
buffer.write(file.getText(
span.start.offset + max(0, stripIndent - span.start.column),
min(file.getOffset(span.start.line + 1) - 1, span.end.offset),
));
try {
buffer.write(file.getText(
span.start.offset + max(0, stripIndent - span.start.column),
min(file.getOffset(span.start.line + 1) - 1, span.end.offset),
));
} catch (e) {
print("Failed to parse span: $name");
rethrow;
}

for (var line = span.start.line + 1; line <= span.end.line; line++) {
buffer.writeln();
Expand Down
210 changes: 111 additions & 99 deletions docs/docs/dart_api/manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ description: Use easier bindings for common queries.

---

## Example Schema

The examples on this page use the following database schema:

{{ load_snippet('before_generation','lib/snippets/setup/database.dart.excerpt.json' ,title="database.dart") }}

See the [tables](./tables.md) documentation for more information on how to define tables.

# Queries

Drift offers three main approaches for querying your database:

- **Manages API**: The Manager API provides a simpler, more intuitive interface for common operations
- **Manager API**: The Manager API provides a simpler, more intuitive interface for common operations
- **Core API**: The Core API provides a more flexible and powerful interface for complex queries
- **Raw SQL**: For those comfortable with SQL, you can write raw SQL queries directly. The SQL is parsed and validated at compile time.

Expand All @@ -25,195 +35,198 @@ This page will cover the Manager API and the Core API. For more information on r
generate_manager: false
```

### Example schema
=== "Manager API"

The examples on this page use the following database schema:
Drift generates a manager for each table in your database. They are accessible through the `managers` property on the database class. Use the `filter()`, `orderBy()`, `withReferences()` and `withFields()` methods to create queries.

{{ load_snippet('before_generation','lib/snippets/setup/database.dart.excerpt.json') }}
**Example:**
{{ load_snippet('manager_example','lib/snippets/dart_api/manager.dart.excerpt.json') }}

=== "Core API"

Use the `select()`, `update()`, `delete()` and `into()` methods on the database class to create queries.

## Read
**Example:**
{{ load_snippet('core_example','lib/snippets/dart_api/manager.dart.excerpt.json') }}


Read on to learn more about writing queries.

## Reads

=== "Manager API"


To select all rows from a table, just call the `get()`/`watch()` method on the table manager. This will return a list of all rows in the table.

{{ load_snippet('manager_select','lib/snippets/dart_api/manager.dart.excerpt.json') }}

=== "Core API"

Write `SELECT` queries using the `select` method on the database class. This method returns a query object which can be used to retrieve rows from the database.

Any query can be run once with `get()` or be turned into an auto-updating stream using `watch()`.

{{ load_snippet('core_select','lib/snippets/dart_api/manager.dart.excerpt.json') }}

### Watching

### Limit and Offset

You can limit the amount of results returned by calling `limit` on queries. The method accepts
the amount of rows to return and an optional offset.
You can watch for changes to the database using the `watch()` method. This will return a stream of results that will emit a new value whenever the underlying data changes.

=== "Manager API"

{{ load_snippet('manager_limit','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('manager_watch','lib/snippets/dart_api/manager.dart.excerpt.json') }}

=== "Core API"

{{ load_snippet('core_limit','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('core_watch','lib/snippets/dart_api/manager.dart.excerpt.json') }}

### Pagination


### Filtering

#### Simple filters

Drift generates prebuilt filters for each column in your table. These filters can be used to filter rows based on the value of a column.
When dealing with a large number of results, consider using pagination to avoid performance issues.

=== "Manager API"

You can apply filters to a query by calling `filter()`. The filter method takes a function that should return a filter on the given table.

{{ load_snippet('manager_filter','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('manager_limit','lib/snippets/dart_api/manager.dart.excerpt.json') }}

=== "Core API"

You can apply filters to a query by calling `where()`. The `where` method takes a function that should map the given table to an `Expression` of boolean. For more details on expressions, see the [expression](./expressions.md) docs.

{{ load_snippet('core_filter','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('core_limit','lib/snippets/dart_api/manager.dart.excerpt.json') }}

#### Complex filters
### Filters

- Use the `&` and `|` operators to combine multiple filters.
- Use `()` to group filters.
- Use `.not` to negate a condition.
Apply filters to your queries to narrow down the results.


=== "Manager API"

{{ load_snippet('manager_complex_filter','lib/snippets/dart_api/manager.dart.excerpt.json') }}
Use the filterset provided in the `filter()` callback to build filters.

<div class="annotate" markdown>
{{ load_snippet('manager_filter','lib/snippets/dart_api/manager.dart.excerpt.json',indent=4) }}
</div>

1. Use the `not()` method to negate a filter.
2. Use the `&` operator to combine filters with an AND operator.
3. Use the `|` operator to combine filters with an OR operator.

=== "Core API"

{{ load_snippet('core_complex_filter','lib/snippets/dart_api/manager.dart.excerpt.json') }}
Use the schema provided in the `where()` callback to build boolean expressions.
See the [Expressions](./expressions.md) documentation for more information.

<div class="annotate" markdown>
{{ load_snippet('core_filter','lib/snippets/dart_api/manager.dart.excerpt.json',indent=4) }}
</div>

1. Use the `not()` method to negate a filter.
2. Use the `&` operator to combine filters with an AND operator.
3. Use the `|` operator to combine filters with an OR operator.

### Referencing other tables

The manager also makes it easy to query an entities referenced fields by using the `withReferences` method.
This will return a record with the entity and a `refs` object which contains the referenced fields.

{{ load_snippet('manager_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
### Ordering

The problem with the above approach is that it will issue a separate query for each row in the result set. This can be very inefficient if you have a large number of rows.
If there were 1000 todos, this would issue 1000 queries to fetch the category for each todo.
Order the results of a query using the `orderBy()` method.

!!! note "Filter on foreign keys"

When filtering on a reference column, drift will apply the filter to the column itself instead of joining the referenced table.
For example, `todos.filter((f) => f.category.id(1))` will filter on the `category` column on the `todos` table, instead of joining the two tables and filtering on the `id` column of the `categories` table.
=== "Manager API"

<h4>How does this affect me?</h4>
<div class="annotate" markdown>
{{ load_snippet('manager_ordering','lib/snippets/dart_api/manager.dart.excerpt.json',indent=4) }}
</div>

If you have foreign keys contraints enabled (`PRAGMA foreign_keys = ON`) this won't affect you. The database will enfore that the `id` column on the `categories` table is the same as the `category` column on the `todos` table.
1. Use the `&` operator to chain multiple orderings.

If you don't have foreign key constraints enabled, you should be aware that the above query will not check that the category with `id` 1 exists. It will only check that the `category` column on the `todos` table is 1.
=== "Core API"

Use the `orderBy()` method to order by multiple columns. The list of orderings are applied from top to bottom.

#### Prefetching references
{{ load_snippet('core_ordering','lib/snippets/dart_api/manager.dart.excerpt.json') }}

Drift provides a way to prefetch references in a single query to avoid inefficient queries. This is done by using the callback in the `withReferences` method. The referenced item will then be available in the referenced managers `prefetchedData` field.
### References

{{ load_snippet('manager_prefetch_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
=== "Manager API"

### Filtering across tables
You can filter across references to other tables by using the generated reference filters. You can nest these as deep as you'd like and the manager will take care of adding the aliased joins behind the scenes.
#### Reads

{{ load_snippet('manager_filter_forward_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
Use the `withReferences()` method to load references in a query. Use `prefetch` to load the references in a single query.

You can also filter across back references. This is useful when you have a one-to-many relationship and want to filter the parent table based on the child table.
<div class="annotate" markdown>
{{ load_snippet('manager_references_read','lib/snippets/dart_api/manager.dart.excerpt.json') }}
</div>

{{ load_snippet('manager_filter_back_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
1. Using `prefetch(category: true)` creates a query which will fetch the `category` for each `todo` in a single query. This is more efficient than fetching each `category` individually.

The code generator will name this filterset using the name of the table that is being referenced. In the above example, the filterset is named `todoItemsRefs`, because the `TodoItems` table is being referenced.
However, you can also specify a custom name for the filterset using the `@ReferenceName(...)` annotation on the foreign key. This may be necessary if you have multiple references to the same table, take the following example:
!!! note ":rotating_light: Avoid Lazy Loading"

{{ load_snippet('user_group_tables','lib/snippets/dart_api/manager.dart.excerpt.json') }}
When using the Manager API, prefer prefetching references over loading them lazily. Lazy loading will execute a separate query for each row in the result set, significantly impacting performance.

We can now use them in a query like this:
However, Lazy loading is useful when you only need the reference after performing some operation on the result set.

{{ load_snippet('manager_filter_custom_back_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
For example, in a Flutter app, you might only want to load all the categories when the user navigates to the category screen. When the user taps on a category, you can then load all the todos associated with that category.

In this example, had we not specified a custom name for the reference, the code generator would have named both filtersets `userRefs` for both references to the `User` table. This would have caused a conflict. By specifying a custom name, we can avoid this issue.
In this instance, prefetching all the categories would be wasteful, as we may never use them.

#### Filter & Order

#### Name Clashes
Drift auto-generates filters and orderings based on the names of your tables and fields. However, many times, there will be duplicates.
When this happens, you will see a warning message from the generator.
To fix this issue, use the `@ReferenceName()` annotation to specify what we should name the filter/orderings.
{{ load_snippet('manager_filter_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}


### Ordering
=== "Core API"

You can also order the results of a query using the `orderBy` method. The syntax is similar to the `filter` method.
Use the `&` to combine multiple orderings. Orderings are applied in the order they are added.
You can also use ordering across multiple tables just like with filters.
!!! tip "Advanced Topic"

{{ load_snippet('manager_ordering','lib/snippets/dart_api/manager.dart.excerpt.json') }}
SQL is a powerful language for querying databases. Most of Drifts Core API closely mirrors SQL itself.
If you find the following section difficult to understand, consider learning more about [joins](https://www.w3schools.com/sql/sql_join.asp/) in SQL before reading this section.

#### Joins

### Count and exists
The manager makes it easy to check if a row exists or to count the number of rows that match a certain condition.
The first step to use a reference in a query is to join the table.
Use the `join()` method with the joins you want to apply. Drift supports `innerJoin()`, `leftOuterJoin()` and `crossJoin()`

{{ load_snippet('manager_count','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('joins','lib/snippets/dart_api/manager.dart.excerpt.json') }}

{{ load_snippet('manager_exists','lib/snippets/dart_api/manager.dart.excerpt.json') }}
#### Read

Once a join has been applied, queries will return a `TypedResult` instead of a data class (e.g. `TodoItem`).
Use the `readTable()` & `readTableOrNull()` methods to read the data class from the `TypedResult`.

## Updates
We can use the manager to update rows in bulk or individual rows that meet a certain condition.
<div class="annotate" markdown>
{{ load_snippet('core_read_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
</div>

{{ load_snippet('manager_update','lib/snippets/dart_api/manager.dart.excerpt.json') }}
1. The schema does not enforce that every `todo` has a `category`. Use `readTableOrNull()` to handle this case.
2. This will execute a separate query for each row in the result set. This should only be used for one off tasks.

We can also replace an entire row with a new one. Or even replace multiple rows at once.
#### Filter & Order

{{ load_snippet('manager_replace','lib/snippets/dart_api/manager.dart.excerpt.json') }}
You can use references in filters and orderings once the table has been joined.

## Creating rows
The manager includes a method for quickly inserting rows into a table.
We can insert a single row or multiple rows at once.
<div class="annotate" markdown>
{{ load_snippet('core_filter_references','lib/snippets/dart_api/manager.dart.excerpt.json') }}
</div>

{{ load_snippet('manager_create','lib/snippets/dart_api/manager.dart.excerpt.json') }}
3. In joins, `useColumns: false` tells drift to not add columns of the joined table to the result set. This is useful here, since we only join the tables so that we can refer to them in the where clause.

#### Aliases

## Deleting rows
We may also delete rows from a table using the manager.
Any rows that meet the specified condition will be deleted.
If a table has more than one foreign key to the same table, use an alias to differentiate between them.

{{ load_snippet('manager_delete','lib/snippets/dart_api/manager.dart.excerpt.json') }}
{{ load_snippet('core_filter_references_alias','lib/snippets/dart_api/manager.dart.excerpt.json') }}

#### Aggregates

Use the `addColumns()` & `groupBy()` methods to add aggregate columns to the query.
Drift has built-in support for `count()`, `sum()`, `avg()`, `min()`, `max()` and `groupConcat()`.

## Computed Fields
{{ load_snippet('core_aggregates','lib/snippets/dart_api/manager.dart.excerpt.json') }}

Manager queries are great when you need to select entire rows from a database table along with their related data. However, there are situations where you might want to perform more complex operations directly within the database for better efficiency.
#### Subqueries

Drift offers strong support for writing SQL expressions. These expressions can be used to filter data, sort results, and perform various calculations directly within your SQL queries. This means you can leverage the full power of SQL to handle complex logic right in the database, making your queries more efficient and your code cleaner.
Drift supports using subqueries to construct a join. This example demonstrates this by using a subquery to only join a subset of the `todoCategory` table.

If you want to learn more about how to write these SQL expressions, please refer to the [expression](expressions.md) documentation.
{{ load_snippet('core_subquery','lib/snippets/dart_api/manager.dart.excerpt.json') }}

{{ load_snippet('manager_annotations','lib/snippets/dart_api/manager.dart.excerpt.json') }}

You can write expressions which reference other columns in the same table or even other tables.
The joins will be created automatically by the manager.


{{ load_snippet('referenced_annotations','lib/snippets/dart_api/manager.dart.excerpt.json') }}

You can also use [aggregate](./expressions.md#aggregate-functions-like-count-and-sum) functions too.

{{ load_snippet('aggregated_annotations','lib/snippets/dart_api/manager.dart.excerpt.json') }}

<!--
This documentation should added once the internal manager APIs are more stable
Expand All @@ -236,7 +249,6 @@ You can also create custom filters that operate on multiple columns by extending
You can create new ordering methods for individual columns types by extending the `ColumnOrdering<T>` class.
Use the `ComposableOrdering` class to create complex orderings.
{{ load_snippet('manager_ordering_extensions','lib/snippets/dart_api/manager.dart.excerpt.json') }}
#### Custom Table Filters
You can also create custom filters that operate on multiple columns by extending generated filtersets.
Expand Down
Loading

0 comments on commit 9f5c62d

Please sign in to comment.