---
title: Generated table rows
description: An overview over the classes drift generates to read from and write to database tables.
---
For each table you define, drift generates two associated classes:
1. A __row class__: This class represents a full row of the table. Drift automatically returns instances of these classes for queries on tables, allowing you to access rows with type safety.
2. A __companion class__: While row classes represent a full row as it appears in the database, sometimes you also need a partial row (e.g. for updates or inserts which don't have values
for auto-incrementing primary keys yet). For this, drift generates a companion class primarily used for inserts and updates.
Drift's row classes come with built-in equality, hashing, and basic serialization support. They also include a `copyWith` method for easy modification.
## Example
A simple table to store usernames shows how the generated row and companion classes behave:
For this table, drift generates a `User` class which roughly looks like this (with a few
additional convenience methods not shown here):
```dart
// Simplified version of the row class generated by drift:
class User {
final int id;
final String username;
const User({required this.id, required this.username});
@override
String toString() {
// ...
}
@override
int get hashCode => Object.hash(id, username);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is User && other.id == this.id && other.username == this.username);
}
```
Note that `User.id` is a non-nullable field, reflecting that the column is also non-nullable
in the database.
When you're inserting a new `User` however, there's no value you could provide to `id` because the
actual value is determined by the database. For this reason, drift also has companion classes to
represent partial rows:
```dart
class UsersCompanion extends UpdateCompanion {
final Value id;
final Value username;
const UsersCompanion({
this.id = const Value.absent(),
this.username = const Value.absent(),
});
UsersCompanion.insert({
this.id = const Value.absent(),
required String username,
}) : username = Value(username);
@override
Map toColumns(bool nullToAbsent) {
// ...
}
}
```
In a companion, all fields are wrapped in a `Value` class that represents whether a column is present
or not.
### Using row classes
With the two generated classes, database rows can be created and read in a type-safe and structured way:
Note that the manager API doesn't use companions and instead has `required` columns as
`required` parameters on the function used to create new rows.
The special `UsersCompanion.insert` constructor has required parameters for all columns that don't
have a default in the database:
## Dataclass Name
By default, 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 make drift use a different name, use the `@DataClassName` annotation:
## Companions and `Value`
Companion classes are used to represent a _partial_ row where not all columns are present.
This class is introduced for two reasons:
1. Dart has a null-safe type system: If we only had a single row class, all values generated by the
database (like `autoIncrement()` columns) would have to be nullable when creating new rows.
That would make the class unfit for queries though.
2. We need a distinction between `NULL` (in SQL) and absent columns: For updates, setting a column to
`NULL` is not the same thing as not changing it at all (and thus keeping the previous value in the database).
There's only one `null` in Dart though, so we need a different structure.
To solve this problem, companions represent partial rows by using Drift's `Value` class.
`Value`s store a value (which can be nullable) or explicitly indicate that a value is _absent_:
1. Since the `id` is `autoIncrement()`, the database will pick a value for us and no value
is provided explicitly. Since `Value.absent()` is also the default, this could be omitted.
2. To simplify the common scenarios of inserts, drift generates a `.insert()` constructor
on companions that avoids `Value` wrappers where they are not required.
This insert could be written as `UsersCompanion.insert(username: 'user')`
### Updating with SQL expressions
Companions also provide a `.custom` method used when mixing values and SQL expressions for
updates or deletes.
For instance, this update statement changes all the names of all rows in the `users` table
to be lower-case:
## Custom dataclass
The generated dataclass works well for most cases, but you might want to use your own class to
represent rows.
This can be useful when these classes should extend, implement or mix-in other classes, or if you
want to apply other builders like `json_serializable` too.
!!! 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`.
In the default configuration, row classes must adhere to the following requirements:
- They must have an unnamed constructor.
- Each constructor argument must have the name of a drift column
(matching the getter name in the table definition).
- The type of a constructor argument must be equal to the type of a column,
including nullability and applied type converters.
On the other hand, note that:
- A custom row class can have additional fields and constructor arguments, as
long as they're not required. Drift will ignore those parameters when mapping
a database row.
- A table can have additional columns not reflected in a custom data class.
Drift will simply not load those columns when mapping a row.
### Using records
To get a very lightweight representation of your database rows, you can instruct drift to generate
matching a record for them:
You can also be explicit about the record type to use:
### Using another constructor
By default, drift will use the default, unnamed constructor to map a row to the class.
If you want to use another constructor, set the `constructor` parameter on the
`@UseRowClass` annotation:
### 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`, where
`T` is the type of your row class itself.
For instance, the previous class could be changed like this:
```dart
class User implements Insertable {
final int id;
final String name;
final DateTime birthDate;
User({required this.id, required this.name, required this.birthDate});
@override
Map toColumns(bool nullToAbsent) {
return UsersCompanion(
id: Value(id),
name: Value(name),
birthDate: Value(birthDate),
).toColumns(nullToAbsent);
}
}
```
As implementing this can be tedious, drift offers the `write_to_columns_mixins` [builder option](../generation_options/index.md). When enabled, drift generates a mixin you can apply on your row class
to implement `toColumns`:
```dart
class User with UsersToColumns {
// ...
}
```
### Static and asynchronous factories
Starting with drift 2.0, the custom constructor set with the `constructor`
parameter on the `@UseRowClass` annotation may also refer to a static method
defined on the class to load.
That method must either return the row class or a `Future` of that type.
Unlike a named constructor or a factory, this can be useful in case the mapping
from SQL to Dart needs to be asynchronous:
```dart
class User {
// ...
static Future load(int id, String name, DateTime birthday) async {
// ...
}
}
```
### Custom dataclass in drift files
To use existing row classes in [drift files](../sql_api/index.md), use the `WITH` keyword at the end of the
table declaration. Also, don't forget to import the Dart file declaring the row
class into the drift file.
```sql
import 'user.dart'; -- or what the Dart file is called
CREATE TABLE users(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
birth_date DATETIME NOT NULL
) WITH User;
```
This feature is also supported for views. Simply add the `WITH ClassName` syntax
after the name of the view in the `CREATE VIEW` statement:
```sql
CREATE VIEW my_view WITH ExistingClass AS SELECT ...
```
You can make drift target named constructors too:
```sql
CREATE TABLE users(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
birth_date DATETIME NOT NULL
) WITH User.myNamedConstructor;
```
#### 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:
```sql
import 'my_existing_class.dart';
/*
Assuming a Dart class like the following:
class MyExistingClass {
final String name;
final double avgAge;
MyExistingClass(this.name, this.avgAge);
}
*/
myQuery WITH MyExistingClass: SELECT name, AVG(age) AS avg_age FROM entries GROUP BY category;
```
Again, you can also target a named constructor:
```sql
/*
class MyExistingClass {
final String name;
final double avgAge;
MyExistingClass.fromSql(this.name, this.avgAge);
}
*/
myQuery WITH MyExistingClass.fromSql: SELECT name, AVG(age) AS avg_age FROM entries GROUP BY category;
```
For your convenience, drift is using different generation strategies even for queries _without_
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)
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)
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.
4. Single-column results: When a select statement only has a single column, but _doesn't represent a full table_,
drift will not generate a full result class for it. Instead, the value is returned directly.
5. Other results: This is probably the most common case in practice: All result sets that don't
fall into one of the existing categorizations are said to have a _normal_ result set.
Depending on what kind of result set your query has, you can use different fields for the existing Dart class:
1. For a nested table selected with `**`, your field needs to store a structure compatible with the result set
the nested column points to. For `my_table.**`, that field could either be the generated row class for `MyTable`
or a custom class as described by rule 3.
2. For nested list results, you have to use a `List`. The `T` has to be compatible with the inner result
set of the `LIST()` as described by these rules.
3. For a single-table result, you can use the table class, regardless of whether the table uses an existing table
class or whether it is generated by drift.
If that matches the intention of your query better, you may also choose to use a _different_ class for
nested tables, provided that all fields of that class can be mapped to a column as described by these rules.
4. For a single-column result, you may either use that type directly or a single-field class wrapping it.
5. For normal results, each field of your result class must match the name of a column in that result set.
The type of the column must be assignable to the field in your class, drift will also take type converters
into account here.
- For a `**` column in a normal result set, see rule 1.
- For a `LIST()` column in a normal result set, see rule 2.
While these rules may seem complicated when entirely spelled out, they are designed to match the
intuitive mapping one would expect.
Consider this example:
Using the rules as defined above, let's see how the `EmployeeWithStaff` class can look like:
The outermost result set has three columns: A `**` column, a simple expression column and a `LIST`
column. That means that this query falls under rule 5.
This essentially means that we get to write a class.
For the simple column that references the `name` column, we know it must be a string because the
column was defined with `TEXT`.
```dart
class EmployeeWithStaff {
final T1 self;
final String supervisor;
final T3 staff;
EmployeeWithStaff(this.self, this.supervisor, this.staff);
}
```
As `self` is a `**` column, rule 1 applies. `self` references a table, `employees`.
By rule 3, this means that `T1` can be a `Employee`, the row class for the `employees` table.
On the other hand, `staff` is a `LIST()` column and rule 2 applies here. This means that `T3` must
be a `List`.
The inner result set of the `LIST` references all columns of `employees` and nothing more, so rule
3 applies. Thus, we can either use `Employee` again, or another custom row class referencing columns
from that table.
The final class can now look like this:
```dart
class IdAndName {
final int id;
final String name;
// This class can be used since id and name column are available from the list query.
// We could have also used the `Employee` class or a record like `(int, String)`.
IdAndName(this.id, this.name);
}
class EmployeeWithStaff {
final Employee self;
final String supervisor;
// We could have also picked List for this field
final List staff;
EmployeeWithStaff(this.self, this.supervisor, this.staff);
}
```
In practice, the rules should be intuitive while also being flexible enough for you to
design the result classes the way you like.
If you have questions about existing result classes, or think you have found an edge-case not
properly handled, please [start a discussion](https://github.com/simolus3/drift/discussions/new) in
the drift repository, thanks!
## JSON serialization
Generated row classes can be converted from and to JSON:
!!! warning "Drift serialization status"
Serialization has been added to drift in a very early version with an unfortunate design that
can only cover simple serialization needs. Advanced JSON options that dedicated packages like
`json_serializable`, `built_value` or `freezed` offer are superior to drift's serialization
capabilities.
Drift implementing serialization capabilities violates separation of concerns, and this feature
will not be expanded.
A better approach is to write your own [custom row classes](#custom-dataclass) and apply another
JSON serialization builder on them.
### Key names
For tables and views defined as Dart classes, drift uses the name of the getter defining columns as
a JSON key:
```json
{
"id": 1,
"title": "Todo 1",
"createdAt": "2024-02-29T12:00:00Z"
}
```
For elements defined in [drift files](../sql_api/drift_files.md), drift uses the name of the column
in SQL instead. This can be a problem when mixing the two declarations. A view defined in a drift file
that selects from the Dart table would have different JSON keys, for instance:
```sql
-- JSON keys are id, title, created_at
CREATE VIEW todos_view AS SELECT * FROM todos;
```
#### Custom json keys
To use a custom name for JSON serialization, use the `@JsonKey` annotation.
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
```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#changing-sql-names) documentation.