Skip to content

Commit

Permalink
some more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
dickermoshe committed Oct 7, 2024
1 parent dfef783 commit 294d63a
Show file tree
Hide file tree
Showing 10 changed files with 566 additions and 96 deletions.
13 changes: 12 additions & 1 deletion docs/builders/src/css_classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ String lookupClassName(DynamicTextStyle cssClass) {
if (className == null) {
throw Exception(
'A class name was not found for a given style. '
'Please add the style to the styles map. '
'Please add the style to the styles map in the css_classes.dart file. '
'Then you must add the class to the CSS file. '
'Style: $cssClass',
);
Expand Down Expand Up @@ -292,6 +292,17 @@ final styles = <DynamicTextStyle, String>{
fontStyle: null,
fontWeight: null,
decoration: null)): "SyntaxHighlight-16",
DynamicTextStyle(
lightStyle: TextStyle(
color: Color(4294901760),
fontStyle: null,
fontWeight: null,
decoration: null),
darkStyle: TextStyle(
color: Color(4294901760),
fontStyle: null,
fontWeight: null,
decoration: null)): "SyntaxHighlight-17"
};

// Small script for generating the CSS file from the styles map.
Expand Down
7 changes: 2 additions & 5 deletions docs/docs/css/syntax_highlight.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,9 @@
.SyntaxHighlight-16 { color: #000088; }
@media (prefers-color-scheme: dark) { .SyntaxHighlight-16 { color: #b9eeff; } }

.SyntaxHighlight-17 { color: #ff0000; }
@media (prefers-color-scheme: dark) { .SyntaxHighlight-17 { color: #ff0000; } }



.md-typeset__table table:not([class]) {
font-size: .74rem;
}



60 changes: 21 additions & 39 deletions docs/docs/queries.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---

title: Queries
title: Query
description: Create, read, update, and delete data in your database.

---

Drift makes it easy to write type-safe queries, ensuring that your database interactions are reliable and error-free. Additionally, Drift can watch for changes in your data, allowing your application to react in real-time to updates, inserts, and deletions.
## Overview

Drift offers 3 main ways to interact with your database:
Drift offers 3 way to write queries:

1. **Manager API**: A simple, high-level API for interacting with your database. The Manager API is the easiest way to get started with Drift, and is perfect for simple queries and updates.
2. **Core API**: A low-level API that allows you to write custom queries and interact with your database in a more fine-grained way.
Expand All @@ -19,16 +19,18 @@ This page will cover the Manager API. For information on the Core API and Type-S
<h2>Manager API</h2>

Drift generates a manager for each table in your database. This manager provides a simple API for creating, reading, updating, and deleting data in your database.
It can be accessed via the `managers` property on the `Database` object.
It should be accessed via the `managers` property on the `Database` object.

Example:

{{ load_snippet('superhero_query','lib/snippets/schema.dart.excerpt.json') }}

If you don't plan to use the Manager API, you can disable it by setting `generate_manager: false` in your `build.yaml` file.

<div class="result" markdown>
!!! note "Disabling the Manager API"

If you don't plan to use the Manager API, you can disable it by setting `generate_manager: false` in your `build.yaml` file.

=== "`build.yaml`"
```yaml
```yaml title="build.yaml"
targets:
$default:
builders:
Expand All @@ -38,32 +40,22 @@ If you don't plan to use the Manager API, you can disable it by setting `generat
# generate_manager: false
```


</div>


## Query Builder

The Manager API provides a query builder that allows you to build complex queries using a fluent API.
By chaining methods together, you can filter, sort, and paginate your data with ease.

Once you have built your query, you can execute it using the [`get`](#get-all-records), [`watch`](#get-all-records), [`update`](#update), [`delete`](#delete), [`count`](#count), or [`exists`](#exists) methods.
Once the query is built, you can execute it using the [`get`](#get-all-records), [`watch`](#get-all-records), [`update`](#update), [`delete`](#delete), [`count`](#count), and [`exists`](#exists) methods.

### Filter

Use the `filter` method to filter records based on a condition.

In Dart, logical operators `&&` (AND) and `||` (OR) are commonly used within parentheses to combine multiple conditions for logical expressions.
```dart
if (condition1 && condition2) {
// do something
}
```
Similarly, in Drift, an additional set of operators, `&` (AND) and `|` (OR), are available to combine conditions.
Use the `&` (AND) and `|` (OR), are available to combine conditions together.

{{ load_snippet('filter','lib/snippets/queries.dart.excerpt.json') }}

To negate a condition, use the `not` method.
To negate a condition, use the `not` method after the `filter` method.

{{ load_snippet('filter-not','lib/snippets/queries.dart.excerpt.json') }}

Expand All @@ -76,7 +68,7 @@ The `&` operator is used to combine multiple sorting conditions.

### Limit

Performing queries that return a large number of records can be inefficient.
Performing queries that return many records at once can be inefficient.
Use the `limit` and `offset` methods to paginate the results.

{{ load_snippet('pagination','lib/snippets/queries.dart.excerpt.json') }}
Expand All @@ -97,22 +89,19 @@ Records can be read/watched using the `get` and `watch` methods provided by the

{{ load_snippet('retrieve_all','lib/snippets/queries.dart.excerpt.json') }}

### Get a single record
### Get Single Record

The following example demonstrates how to retrieve a single record:
To retrieve a single record, use the `getSingle`/ `watchSingle` method.

{{ load_snippet('retrieve_single','lib/snippets/queries.dart.excerpt.json') }}

Drift provides helper methods for retrieving singletons:
This method will throw an exception if no records are found. If you want to handle this case gracefully, use the `getSingleOrNull`/ `watchSingleOrNull` method instead.

- `getSingle` - Retrieve a single record, throwing an exception if exactly one record is not found.
- `getSingleOrNull` - Retrieve a single record, throwing an exception if more than one record is found.
- `watchSingle` - Same as `getSingle`, but returns a stream of the record.
- `watchSingleOrNull` - Same as `getSingleOrNull`, but returns a stream of the record.
{{ load_snippet('retrieve_single_or_null','lib/snippets/queries.dart.excerpt.json') }}

### Distinct

When you perform complex queries that involve multiple tables, it is possible that duplicate records will be returned.
When performing complex queries, you may encounter duplicate records in the result set.

To avoid this, set `distinct: true`. This will remove duplicates.

Expand All @@ -126,7 +115,7 @@ Drift makes it easy to retrieve referenced fields from other tables using the `w

{{ load_snippet('with-references-summary','lib/snippets/queries.dart.excerpt.json') }}

See the [Read References](referenced-queries.md) documentation for more information on referencing other tables.
See the [Read References](references.md#query-references) documentation for more information on referencing other tables.

## Update

Expand All @@ -152,11 +141,6 @@ Use the `count` method to count the number of records in a table.

{{ load_snippet('manager_count','lib/snippets/queries.dart.excerpt.json') }}

!!! note "Distinct"
When counting records, `distinct` is set to `true` by default.
This differs from the `get` and `watch` methods, where `distinct` is set to `false` by default.
See the [Distinct](#distinct) section for more information.

## Exists

To check if any records exist that match a certain condition, use the `exists` method.
Expand All @@ -169,8 +153,7 @@ To create a new record, use the `create` method on the manager. This method will
{ .annotate }

1. If the primary key of the table is an auto-incrementing integer, the ID will be the value of the primary key.
If the primary key is anything else, Drift adds a column named `rowid` to the table and returns the value of that column.

If the primary key is anything else, the `rowid` of the new record will be returned.

{{ load_snippet('manager_simple_create_single','lib/snippets/queries.dart.excerpt.json') }}

Expand Down Expand Up @@ -213,7 +196,6 @@ You can use the `create` method to perform an upsert operation. An upsert combin

Note that this is a lower-level operation and is part of the Core API. For more details, refer to the [Core API](/docs/core) page.


## Performance Considerations

Database operations on a very large dataset can be time-consuming and may block the main thread, causing your application to become unresponsive.
Expand Down
153 changes: 153 additions & 0 deletions docs/docs/references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
---

title: Relationship
description: Define relationships between tables.

---

## Overview

A foreign key is a column in one table that refers to the primary key in another table. It establishes a link between two tables, creating a relationship between them.

#### Example

Consider the relationship between a `User` table and a `Group` table.
Each user belongs to a group, and each group can have multiple users.

**Group Table**

| id | group_name |
| --- | ---------- |
| 1 | Admin |
| 2 | User |

**User Table**

| id | user_name | group |
| --- | --------- | ----- |
| 1 | Alice | 1 |
| 2 | Bob | 2 |
| 3 | Carol | 2 |

Here, the `group` column in the User table is a foreign key referencing `id` in the Group table.

The following sections will show how to define these relationships using Drift.

## Many-to-One Relationships

To define a reference, use the `references` method in the table schema.
This method takes 2 arguments:

- The table that the reference points to.
- The column in the other table that the reference points to.

Here's how the `User` table from the example above could be defined:
<div class="annotate">
{{ load_snippet('user_group_schema','lib/snippets/references.dart.excerpt.json') }}
</div>
1. This `@ReferenceName("users") `annotation is optional, It's used to name the reference in the generated code. If you don't provide it, one will be generated for you.
2. This `#id` syntax may look unfamiliar to you. It's a uncommonly used syntax in Dart called [Symbol literals](https://dart.dev/guides/language/language-tour#symbols).

## Constraints

By default, SQLite does not enforce foreign key constraints.
Meaning, if you were to create a user with a group that does not exist, SQLite would allow it.

**This is not ideal for maintaining data integrity.**

To enable foreign key constraints in your database, add the custom SQL statement `PRAGMA foreign_keys = ON;` to the `beforeOpen` callback of your database's migration as follows:

{{ load_snippet('foreign_keys_on','lib/snippets/references.dart.excerpt.json') }}

For more details on common pragmas and additional database configuration options, refer to the [Database]() documentation.

#### Violating Constraints

By default, if [foreign key constraints](#foreign-key-constraints) are enabled and a foreign key constraint is violated, an exception will be thrown.

These constraints can be violated by:

- Deleting a row in the referenced table. (e.g A group is deleted, but users still reference it.)

- Updating the primary key in the referenced table. (e.g A group's ID is changed, but users still reference the old ID.)

This behavior can be customized using the `onDelete` and `onUpdate` parameters in the `references` method.

<div class="annotate" markdown>

- **`KeyAction.cascade`**: The referenced rows are deleted or updated when the referenced row is deleted or updated.
- **`KeyAction.setDefault`** & **`KeyAction.setNull`**: The referenced column is set to a default value or `null` when the referenced row is deleted or updated.
- **`KeyAction.noAction`** (default) & **`KeyAction.restrict`**: An exception is thrown when the referenced row is deleted or updated. (1)

</div>

1. The only difference between `KeyAction.noAction` and `KeyAction.restrict` is that deferred foreign key constraints will still be enforced mid-transaction with `KeyAction.restrict`.

## Query References

When fetching data from the database, you can use the `withReferences` method to fetch references along with the main table.

{{ load_snippet('manager_references','lib/snippets/references.dart.excerpt.json') }}

#### Prefetching references

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.

{{ load_snippet('manager_prefetch_references','lib/snippets/references.dart.excerpt.json') }}

## Filtering and Ordering

Filters may be applied from the many side of the relationship. For example, to find todos of a specific category:

{{ load_snippet('manager_filter_forward_references','lib/snippets/references.dart.excerpt.json') }}

And from the one side of the relationship. For example, to find the category of a specific todo:

{{ load_snippet('manager_filter_back_references','lib/snippets/references.dart.excerpt.json') }}

The same is true for ordering:

{{ load_snippet('manager_order_forward_references','lib/snippets/references.dart.excerpt.json') }}

!!! info "Filtering on Reference Columns"

**If you have [foreign key constraints enabled](#constraints), filtering on reference columns works as expected.**

However, without foreign key constraints, there are special considerations:

1. Filters on reference columns apply to the local column, not the referenced table.
2. Example: `todos.filter((f) => f.category.id(1))` filters on the `category` column in `todos`, not `id` in `categories`.
3. This doesn't verify if the referenced category actually exists.

To ensure data integrity and expected behavior, always enable foreign key constraints.

## Many-to-Many Relationships

Drift does not support many-to-many relationships directly. Instead, you can create a junction table to represent the relationship.

For example, consider a many-to-many relationship between `Books` and `Tags` tables. A book can have multiple tags, and a tag can be associated with multiple books.

This relationship can be represented using a junction table `TagBookRelationship`:

{{ load_snippet('many_to_many_schema','lib/snippets/references.dart.excerpt.json') }}

You can now use this table to query books by tag or tags by book.

{{ load_snippet('many_to_many_usage','lib/snippets/references.dart.excerpt.json') }}


## Advanced

### Deferred Constraints

By default, foreign key constraints are checked immediately. So the following code will throw an exception:

{{ load_snippet('deferred_constraints','lib/snippets/references.dart.excerpt.json') }}

Although an author with id `f7b3b3e0...` will exist at the end of the transaction, the foreign key constraint is checked immediately after the insert, causing an exception.

To defer the constraint check until the end of the transaction, use the `initiallyDeferred` parameter in the `references` method:

{{ load_snippet('define_deferred_constraints','lib/snippets/references.dart.excerpt.json') }}

Now, the foreign key constraint will be checked at the end of the transaction, allowing the code to run without throwing an exception.
Loading

0 comments on commit 294d63a

Please sign in to comment.