Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New schema docs #3290

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions docs/docs/Internals/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---

title: Drift internals
description: Work in progress documentation on drift internals
title: Install from GitHub
description: How to install drift from GitHub

---

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/Migrations/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ callback. However, the callbacks also give you an instance of `Migrator` as a
parameter. This class knows about the target schema of the database and can be
used to create, drop and alter most elements in your schema.

## General tips
## General tips

To ensure your schema stays consistent during a migration, you can wrap it in a `transaction` block.
However, be aware that some pragmas (including `foreign_keys`) can't be changed inside transactions.
Still, it can be useful to:

- always re-enable foreign keys before using the database, by enabling them in [`beforeOpen`](#post-migration-callbacks).
- disable foreign-keys before migrations
- run migrations inside a transaction
- always re-enable foreign keys before using the database, by enabling them in `beforeOpen`.
- disable foreign-keys before migrations.
- run migrations inside a transaction.
- make sure your migrations didn't introduce any inconsistencies with `PRAGMA foreign_key_check`.

With all of this combined, a migration callback can look like this:
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/Migrations/step_by_step.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ spanning many versions, this can quickly lead to code that's hard to understand
The `stepByStep` function generated by the `drift_dev schema steps` command gives you an
`OnUpgrade` callback.
But you might want to customize the upgrade behavior, for instance by adding foreign key
checks afterwards (as described in [tips](index.md#general-tips)).
checks afterwards.

The `Migrator.runMigrationSteps` helper method can be used for that, as this example
shows:
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/_redirects
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@
/docs/platforms/postgres/ /Platforms/postgres/
/docs/advanced-features/daos/ /dart_api/daos/
/api/* https://pub.dev/documentation/drift/latest/index.html
https://drift.simonbinder.eu/custom_row_classes/ /dart_api/dataclass/
https://drift.simonbinder.eu/type_converters/ /dart_api/tables/#custom-types
Comment on lines +56 to +57
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these be without the domain like the others above?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Remove the leading url


3 changes: 3 additions & 0 deletions docs/docs/css/syntax_highlight.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@

.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; } }
194 changes: 131 additions & 63 deletions docs/docs/custom_row_classes.md → docs/docs/dart_api/dataclass.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,113 @@
---

title: Custom row classes
description: Use your own classes as data classes for drift tables
title: Dataclass
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not too happy with the generic Dataclass name, does "Generated classes" with a description like "Overview of classes generated by drift for reading and writing to the database" work?

description: Dataclass for reading and writing data to the database.

---

For each table declared in Dart or in a drift file, `drift_dev` generates a row class (sometimes also referred to as _data class_)
to hold a full row and a companion class for updates and inserts.
This works well for most cases: Drift knows what columns your table has, and it can generate a simple class for all of that.
In some cases, you might want to customize the generated classes though.
For instance, you might want to add a mixin, let it extend another class or interface, or use other builders like
`json_serializable` to customize how it gets serialized to json.

As a solution, drift allows you to use your own classes as data classes for the database.

## Using custom classes
# Generated Dataclass

To use a custom row class, simply annotate your table definition with `@UseRowClass`.
Drift generates a dataclass for each table in your database. These dataclasses represent query results and come with built-in equality, hashing, and serialization support. They also include a `copyWith` method for easy modification.

**Example:**

For a `Users` table, Drift automatically generates a `User` dataclass. This dataclass is used for all read operations from the `Users` table, ensuring type-safe and structured data retrieval.

{{ load_snippet('generated-dataclass','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

## Dataclass Name

The dataclass name is derived from the table name.

- If the name ends in `s`, the dataclass name will be the name with `s` removed.
- Example: `Users` -> `User`
- Otherwise, the dataclass name will be the name with `Data` appended.
- Example: `UserInfo` -> `UserInfoData`


To use a custom name use the `@DataClassName` annotation.

**Example:**

{{ load_snippet('data-class-name','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

## Json serialization
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should not be this far up - and there should be a little warning saying that using custom row classes is the preferred approach for JSON serialization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drift is a relational persistence library, and ideally shouldn't have to do anything to do with serialization. It's a historical artifact that I regret, but it's too late to fix this now.

Our serialization capabilities are incredibly restricted compared to packages that actually intend to do serialziation, which is why I recommend using custom row classes to combine drift with other libraries where necessary.


### Key names

When serializing to json, the generated dataclass will use the column name in `snake_case` for the json keys.

**Example:**

{{ load_snippet('default-json-keys','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

```json
{
"id": 1,
"title": "Todo 1",
"created_at": "2024-02-29T12:00:00Z"
}
```

### Custom json keys

To use a custom name for JSON serialization, use the `@JsonKey` annotation.
dickermoshe marked this conversation as resolved.
Show resolved Hide resolved
Note that the `@JsonKey` class from `package:drift` is not same as the `@JsonKey` annotation from `package:json_annotation`, and the two are not compatible with each other.

**Example:**

{{ load_snippet('custom-json-keys','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}

```json
{
"id": 1,
"title": "Todo 1",
"created": "2024-02-29T12:00:00Z"
}
```

If you prefer to use the actual column name in SQL as the JSON key, set `use_sql_column_name_as_json_key` to `true` in the `build.yaml` file.

```yaml title="build.yaml"
targets:
$default:
builders:
drift_dev:
options:
use_sql_column_name_as_json_key : true
```
For more details on customizing column names in SQL, refer to the [column name](tables.md#column-names) documentation.

## Companions

In addition to the generated dataclass representing a complete row, Drift also generates a companion object for each table, which represents a partial row and can be used to update existing rows.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a pretty short explanation for an important subject. I think we should take something like this as a better reference on how to introduce companions.

We could also mention that companions are not as relevant with the manager API as their structure is reflected by the callbacks for creating and updating rows. (similar to the annotation below)


<div class="annotate" markdown>

{{ load_snippet('generated-companion','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use a tabbed view here with Manager vs. the Core query builder instead of having it in a single snippet.


</div>
1. `o()` is just a helper function that creates a `UsersCompanion`.

### Value object
When using the companion object to update a row, optional fields must be wrapped in a `Value` object. This is used by Drift to distinguish between `null` and not present values.

{{ load_snippet('generated-value','lib/snippets/dart_api/dataclass.dart.excerpt.json') }}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example really isn't too helpful. Values were also used in the example above for updates, perhaps we could reference that here.

This is used by Drift to distinguish between null and not present values.

A better explanation would be to mention what drift actually does with that: For updates, there's a distinction between setting the value of a column to null vs. not touching it at all. We can't represent without introducing some kind of optional structure, which is what Value is.


## Custom dataclass

The generated dataclass works well for most cases, but you might want to use your own class as a dataclass for a table.

For instance, you might want to add a mixin, let it extend another class or interface, or use other builders like `json_serializable` to customize how it gets serialized to json.

!!! note "Row Class"

In the documentation, we use the terms _row class_ and _dataclass_ interchangeably.
Both refer to a class that represents a row of a database table.

To use a custom row class, simply annotate your table definition with `@UseRowClass`.

{{ load_snippet('start','lib/snippets/custom_row_classes/default.dart.excerpt.json','lib/snippets/custom_row_classes/named.dart.excerpt.json') }}

Expand Down Expand Up @@ -46,6 +136,32 @@ If you want to use another constructor, set the `constructor` parameter on the

{{ load_snippet('named','lib/snippets/custom_row_classes/default.dart.excerpt.json','lib/snippets/custom_row_classes/named.dart.excerpt.json') }}

### Custom companions

In most cases, generated companion classes are the right tool for updates and inserts.
If you prefer to use your custom row class for inserts, just make it implement `Insertable<T>`, where
`T` is the tye of your row class itself.
For instance, the previous class could be changed like this:

```dart
class User implements Insertable<User> {
final int id;
final String name;
final DateTime birthDate;

User({required this.id, required this.name, required this.birthDate});

@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return UsersCompanion(
id: Value(id),
name: Value(name),
birthDate: Value(birthDate),
).toColumns(nullToAbsent);
}
}
```

### Static and asynchronous factories

Starting with drift 2.0, the custom constructor set with the `constructor`
Expand All @@ -65,7 +181,7 @@ class User {
}
```

### Existing row classes in drift files
### Custom dataclass in drift files

To use existing row classes in drift files, use the `WITH` keyword at the end of the
table declaration. Also, don't forget to import the Dart file declaring the row
Expand Down Expand Up @@ -98,33 +214,7 @@ CREATE TABLE users(
) WITH User.myNamedConstructor;
```

## Inserts and updates with custom classes

In most cases, generated companion classes are the right tool for updates and inserts.
If you prefer to use your custom row class for inserts, just make it implement `Insertable<T>`, where
`T` is the name of your row class itself.
For instance, the previous class could be changed like this:

```dart
class User implements Insertable<User> {
final int id;
final String name;
final DateTime birthDate;

User({required this.id, required this.name, required this.birthDate});

@override
Map<String, Expression> toColumns(bool nullToAbsent) {
return UsersCompanion(
id: Value(id),
name: Value(name),
birthDate: Value(birthDate),
).toColumns(nullToAbsent);
}
}
```

## Existing row classes for queries
#### Custom dataclass for queries

Existing row classes may also be applied to named queries defined in a `.drift` file.
They have a similar syntax, adding the `WITH` keyword after the name of the query:
Expand Down Expand Up @@ -165,10 +255,10 @@ For your convenience, drift is using different generation strategies even for qu
an existing row class. It is helpful to enumerate them because they affect the allowed type for
fields in existing types as well.

1. Nested tables: When the [`SELECT table.**` syntax](sql_api/drift_files.md#nested-results)
1. Nested tables: When the [`SELECT table.**` syntax](../sql_api/drift_files.md#nested-results)
is used in a query, drift will pack columns from `table` into a nested object instead of generating fields
for every column.
2. Nested list results: The [`LIST()` macro](sql_api/drift_files.md#list-subqueries)
2. Nested list results: The [`LIST()` macro](../sql_api/drift_files.md#list-subqueries)
can be used to expose results of a subquery as a list.
3. Single-table results: When a select statement reads all columns from a table (and no additional columns),
like in `SELECT * FROM table`, drift will use the data class of the table instead of generating a new one.
Expand Down Expand Up @@ -257,25 +347,3 @@ If you have questions about existing result classes, or think you have found an
properly handled, please [start a discussion](https://github.com/simolus3/drift/discussions/new) in
the drift repository, thanks!

## When custom classes make sense

The default drift-generated classes are a good default for most applications.
In some advanced use-cases, custom classes can be a better alternative though:

- Reduce generated code size: Due to historical reasons and backwards-compatibility, drift's classes
contain a number of methods for json serialization and `copyWith` that might not be necessary
for all users.
Custom row classes can reduce bloat here.
- Custom superclasses: A custom row class can extend and class and implement or mix-in other classes
as desired.
- Other code generators: Since you control the row class, you can make better use of other builders like
`json_serializable` or `built_value`.

## Limitations

These restrictions will be gradually lifted in upcoming drift versions. Follow [#1134](https://github.com/simolus3/drift/issues/1134) for details.

For now, this feature is subject to the following limitations:

- In drift files, you can only use the default unnamed constructor

14 changes: 14 additions & 0 deletions docs/docs/dart_api/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ bitwise operations:

{{ load_snippet('bitwise','lib/snippets/dart_api/expressions.dart.excerpt.json') }}

### BigInt

You may want to cast an expression to a `BigInt` if:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
You may want to cast an expression to a `BigInt` if:
You may want to cast an expression to a `BigInt` if both of these apply to your app:


- The result of an arithmetic operation will be extremely large[^1].
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of being imprecise here? We can just write

Suggested change
- The result of an arithmetic operation will be extremely large[^1].
- The result of an arithmetic operation is not representable by a `double` (i.e., it is larger than `2^52`).

- You are compiling to JavaScript.

[^1]: Like bigger than 4,503,599,627,370,496!

Using `dartCast<BigInt>()` will ensure that the result is interpreted as a `BigInt` by drift.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also note that this doesn't change the behavior in SQL at all.


**Example:**
For an expression `(table.columnA * table.columnB).dartCast<BigInt>()`, drift will report the resulting value as a `BigInt` even if `columnA` and `columnB` were defined as regular integers.

## Null checks
To check whether an expression evaluates to `NULL` in SQL, you can use the `isNull` extension:

Expand Down
8 changes: 1 addition & 7 deletions docs/docs/dart_api/manager.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
---

title: Manager
title: Queries
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should leave this as-is before the restructuring is more complete. At the moment we'd then effectively have multiple pages on queries which is more confusing than before.

We can name it "Queries with Manager" or something and also point out in the introduction/getting started guide that there are two ways to write these queries (like we already do on the new table page).

description: Use easier bindings for common queries.

---



With generated code, drift allows writing SQL queries in type-safe Dart.
While this is provides lots of flexibility, it requires familiarity with SQL.
As a simpler alternative, drift 2.18 introduced a new set of APIs designed to
make common queries much easier to write.

The examples on this page use the database from the [setup](../setup.md)
instructions.

Expand Down
Loading
Loading