Merge branch 'simple-schema-versions' into develop

This commit is contained in:
Simon Binder 2023-07-01 18:31:59 +02:00
commit 2420c8d7c6
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
44 changed files with 2375 additions and 1785 deletions

View File

@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}

View File

@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"due_date","getter_name":"dueDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}

View File

@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"todos","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"title","getter_name":"title","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":6,"max":10}}]},{"name":"body","getter_name":"content","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"category","getter_name":"category","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"due_date","getter_name":"dueDate","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"priority","getter_name":"priority","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}

View File

@ -1,5 +1,11 @@
import 'package:drift/drift.dart';
// #docregion stepbystep
// This file was generated by `drift_dev schema steps drift_schemas lib/database/schema_versions.dart
import 'schema_versions.dart';
// #enddocregion stepbystep
part 'migrations.g.dart';
const kDebugMode = false;
@ -91,3 +97,27 @@ class Example extends _$Example {
// #enddocregion change_type
}
}
class StepByStep {
// #docregion stepbystep
MigrationStrategy get migration {
return MigrationStrategy(
onCreate: (Migrator m) async {
await m.createAll();
},
onUpgrade: stepByStep(
from1To2: (m, schema) async {
// we added the dueDate property in the change from version 1 to
// version 2
await m.addColumn(schema.todos, schema.todos.dueDate);
},
from2To3: (m, schema) async {
// we added the priority property in the change from version 1 or 2
// to version 3
await m.addColumn(schema.todos, schema.todos.priority);
},
),
);
}
// #enddocregion stepbystep
}

View File

@ -0,0 +1,129 @@
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class _S2 extends i0.VersionedSchema {
_S2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
todos,
];
late final Shape0 todos = Shape0(
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
],
attachedDatabase: database,
),
alias: null);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get content =>
columnsByName['body']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get category =>
columnsByName['category']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get dueDate =>
columnsByName['due_date']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('title', aliasedName, false,
additionalChecks: i1.GeneratedColumn.checkTextLength(
minTextLength: 6, maxTextLength: 10),
type: i1.DriftSqlType.string);
i1.GeneratedColumn<String> _column_2(String aliasedName) =>
i1.GeneratedColumn<String>('body', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<int> _column_3(String aliasedName) =>
i1.GeneratedColumn<int>('category', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<DateTime> _column_4(String aliasedName) =>
i1.GeneratedColumn<DateTime>('due_date', aliasedName, true,
type: i1.DriftSqlType.dateTime);
final class _S3 extends i0.VersionedSchema {
_S3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
todos,
];
late final Shape1 todos = Shape1(
source: i0.VersionedTable(
entityName: 'todos',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get content =>
columnsByName['body']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get category =>
columnsByName['category']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get dueDate =>
columnsByName['due_date']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get priority =>
columnsByName['priority']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_5(String aliasedName) =>
i1.GeneratedColumn<int>('priority', aliasedName, true,
type: i1.DriftSqlType.int);
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, _S2 schema) from1To2,
required Future<void> Function(i1.Migrator m, _S3 schema) from2To3,
}) {
return i1.Migrator.stepByStepHelper(step: (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = _S2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
case 2:
final schema = _S3(database: database);
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
});
}

View File

@ -13,11 +13,13 @@ New features need new columns or tables, and outdated columns may have to be alt
removed altogether.
When making changes to your database schema, you need to write migrations enabling users with
an old version of your app to convert to the database expected by the latest version.
Drift provides a set of APIs to make writing migrations easy.
With incorrect migrations, your database ends up in an inconsistent state which can cause crashes
or data loss. This is why drift provides dedicated test tools and APIs to make writing migrations
easy and safe.
{% assign snippets = 'package:drift_docs/snippets/migrations/migrations.dart.excerpt.json' | readString | json_decode %}
## Basics
## Manual setup {#basics}
Drift provides a migration API that can be used to gradually apply schema changes after bumping
the `schemaVersion` getter inside the `Database` class. To use it, override the `migration`
@ -43,7 +45,7 @@ you've actually added the column. In general, try to avoid running queries in mi
`sqlite` can feel a bit limiting when it comes to migrations - there only are methods to create tables and columns.
Existing columns can't be altered or removed. A workaround is described [here](https://stackoverflow.com/a/805508), it
can be used together with `customStatement` to run the statements.
Alternatively, [complex migrations](#complex-migrations) help automating this.
Alternatively, [complex migrations](#complex-migrations) described on this page help automating this.
### Tips
@ -60,6 +62,241 @@ With all of this combined, a migration callback can look like this:
{% include "blocks/snippet" snippets = snippets name = 'structured' %}
## Migration workflow
While migrations can be written manually without additional help from drift, dedicated tools testing your migrations help
to ensure that they are correct and aren't loosing any data.
Drift's migration tooling consists of the following steps:
1. After each change to your schema, use a tool to export the current schema into a separate file.
2. Use a drift tool to generate test code able to verify that your migrations are bringing the database
into the expected schema.
3. Use generated code to make writing schema migrations easier.
### Setup
As described by the first step, you can export the schema of your database into a JSON file.
It is recommended to do this once intially, and then again each time you change your schema
and increase the `schemaVersion` getter in the database.
You should store these exported files in your repository and include them in source control.
This guide assumes a top-level `drift_schemas/` folder in your project, like this:
```
my_app
.../
lib/
database/
database.dart
database.g.dart
test/
generated_migrations/
schema.dart
schema_v1.dart
schema_v2.dart
drift_schemas/
drift_schema_v1.json
drift_schema_v2.json
pubspec.yaml
```
Of course, you can also use another folder or a subfolder somewhere if that suits your workflow
better.
{% block "blocks/alert" title="Examples available" %}
Exporting schemas and generating code for them can't be done with `build_runner` alone, which is
why this setup described here is necessary.
We hope it's worth it though! Verifying migrations can give you confidence that you won't run
into issues after changing your database.
If you get stuck along the way, don't hesitate to [open a discussion about it](https://github.com/simolus3/drift/discussions).
Also there are two examples in the drift repository which may be useful as a reference:
- A [Flutter app](https://github.com/simolus3/drift/tree/latest-release/examples/app)
- An [example specific to migrations](https://github.com/simolus3/drift/tree/latest-release/examples/migrations_example).
{% endblock %}
#### Exporting the schema
To begin, lets create the first schema representation:
```
$ mkdir drift_schemas
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/
```
This instructs the generator to look at the database defined in `lib/database/database.dart` and extract
its schema into the new folder.
After making a change to your database schema, you can run the command again. For instance, let's say we
made a change to our tables and increased the `schemaVersion` to `2`. To dump the new schema, just run the
command again:
```
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/
```
You'll need to run this command every time you change the schema of your database and increment the `schemaVersion`.
Drift will name the files in the folder `drift_schema_vX.json`, where `X` is the current `schemaVersion` of your
database.
If drift is unable to extract the version from your `schemaVersion` getter, provide the full path explicitly:
```
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/drift_schema_v3.json
```
{% block "blocks/alert" title='<i class="fas fa-lightbulb"></i> Dumping a database' color="success" %}
If, instead of exporting the schema of a database class, you want to export the schema of an existing sqlite3
database file, you can do that as well! `drift_dev schema dump` recognizes a sqlite3 database file as its first
argument and can extract the relevant schema from there.
{% endblock %}
### Generating step-by-step migrations {#step-by-step}
With all your database schemas exported into a folder, drift can generate code that makes it much
easier to write schema migrations "step-by-step" (incrementally from each version to the next one).
This code is stored in a single-file, which you can generate like this:
```
$ dart run drift_dev schema steps drift_schemas/ lib/database/schema_versions.dart
```
The generated code contains a `stepByStep` method which you can use as a callback to the `onUpgrade`
parameter of your `MigrationStrategy`.
As an example, here is the [initial](#basics) migration shown at the top of this page, but rewritten using
the generated `stepByStep` function:
{% include "blocks/snippet" snippets = snippets name = 'stepbystep' %}
`stepByStep` expects a callback for each schema upgrade responsible for running the partial migration.
That callback receives two parameters: A migrator `m` (similar to the regular migrator you'd get for
`onUpgrade` callbacks) and a `schema` parameter that gives you access to the schema at the version you're
migrating to.
For instance, in the `from1To2` function, `schema` provides getters for the database schema at version 2.
The migrator passed to the function is also set up to consider that specific version by default.
A call to `m.recreateAllViews()` would re-create views at the expected state of schema version 2, for instance.
### Writing tests
After you've exported the database schemas into a folder, you can generate old versions of your database class
based on those schema files.
For verifications, drift will generate a much smaller database implementation that can only be used to
test migrations.
You can put this test code whereever you want, but it makes sense to put it in a subfolder of `test/`.
If we wanted to write them to `test/generated_migrations/`, we could use
```
$ dart run drift_dev schema generate drift_schemas/ test/generated_migrations/
```
After that setup, it's finally time to write some tests! For instance, a test could look like this:
```dart
import 'package:my_app/database/database.dart';
import 'package:test/test.dart';
import 'package:drift_dev/api/migrations.dart';
// The generated directory from before.
import 'generated_migrations/schema.dart';
void main() {
late SchemaVerifier verifier;
setUpAll(() {
// GeneratedHelper() was generated by drift, the verifier is an api
// provided by drift_dev.
verifier = SchemaVerifier(GeneratedHelper());
});
test('upgrade from v1 to v2', () async {
// Use startAt(1) to obtain a database connection with all tables
// from the v1 schema.
final connection = await verifier.startAt(1);
final db = MyDatabase(connection);
// Use this to run a migration to v2 and then validate that the
// database has the expected schema.
await verifier.migrateAndValidate(db, 2);
});
}
```
In general, a test looks like this:
1. Use `verifier.startAt()` to obtain a [connection](https://drift.simonbinder.eu/api/drift/databaseconnection-class)
to a database with an initial schema.
This database contains all your tables, indices and triggers from that version, created by using `Migrator.createAll`.
2. Create your application database with that connection - you can forward the `DatabaseConnection` to the
`GeneratedDatabase.connect()` constructor on the parent class for this.
3. Call `verifier.migrateAndValidate(db, version)`. This will initiate a migration towards the target version (here, `2`).
Unlike the database created by `startAt`, this uses the migration logic you wrote for your database.
`migrateAndValidate` will extract all `CREATE` statement from the `sqlite_schema` table and semantically compare them.
If it sees anything unexpected, it will throw a `SchemaMismatch` exception to fail your test.
{% block "blocks/alert" title="Writing testable migrations" %}
To test migrations _towards_ an old schema version (e.g. from `v1` to `v2` if your current version is `v3`),
you're `onUpgrade` handler must be capable of upgrading to a version older than the current `schemaVersion`.
For this, check the `to` parameter of the `onUpgrade` callback to run a different migration if necessary.
{% endblock %}
#### Verifying data integrity
In addition to the changes made in your table structure, its useful to ensure that data that was present before a migration
is still there after it ran.
You can use `schemaAt` to obtain a raw `Database` from the `sqlite3` package in addition to a connection.
This can be used to insert data before a migration. After the migration ran, you can then check that the data is still there.
Note that you can't use the regular database class from you app for this, since its data classes always expect the latest
schema. However, you can instruct drift to generate older snapshots of your data classes and companions for this purpose.
To enable this feature, pass the `--data-classes` and `--companions` command-line arguments to the `drift_dev schema generate`
command:
```
$ dart run drift_dev schema generate --data-classes --companions drift_schemas/ test/generated_migrations/
```
Then, you can import the generated classes with an alias:
```dart
import 'generated_migrations/schema_v1.dart' as v1;
import 'generated_migrations/schema_v2.dart' as v2;
```
This can then be used to manually create and verify data at a specific version:
```dart
void main() {
// ...
test('upgrade from v1 to v2', () async {
final schema = await verifier.schemaAt(1);
// Add some data to the users table, which only has an id column at v1
final oldDb = v1.DatabaseAtV1.connect(schema.newConnection());
await oldDb.into(oldDb.users).insert(const v1.UsersCompanion(id: Value(1)));
await oldDb.close();
// Run the migration and verify that it adds the name column.
final db = Database(schema.newConnection());
await verifier.migrateAndValidate(db, 2);
await db.close();
// Make sure the user is still here
final migratedDb = v2.DatabaseAtV2.connect(schema.newConnection());
final user = await migratedDb.select(migratedDb.users).getSingle();
expect(user.id, 1);
expect(user.name, 'no name'); // default from the migration
await migratedDb.close();
});
}
```
## Complex migrations
Sqlite has builtin statements for simple changes, like adding columns or dropping entire tables.
@ -235,208 +472,6 @@ the database file and will re-create it when installing the app again.
You can also delete and re-create all tables every time your app is opened, see [this comment](https://github.com/simolus3/drift/issues/188#issuecomment-542682912)
on how that can be achieved.
## Verifying migrations
Drift contains **experimental** support to verify the integrity of your migrations in unit tests.
To support this feature, drift can help you generate
- a json representation of your database schema
- test databases operating on an older schema version
By using those test databases, drift can help you test migrations from and to any schema version.
{% block "blocks/alert" title="Complex topic ahead" %}
> Writing schema tests is an advanced topic that requires a fairly complex setup described here.
If you get stuck along the way, don't hesitate to [open a discussion about it](https://github.com/simolus3/drift/discussions).
Also, there's a working example [in the drift repository](https://github.com/simolus3/drift/tree/latest-release/examples/migrations_example).
{% endblock %}
### Setup
To use this feature, drift needs to know all schemas of your database. A schema is the set of all tables, triggers
and indices that you use in your database.
You can use the [CLI tools]({{ "../CLI.md" | pageUrl }}) to export a json representation of your schema.
In this guide, we'll assume a file layout like the following, where `my_app` is the root folder of your project:
```
my_app
.../
lib/
database/
database.dart
database.g.dart
test/
generated_migrations/
schema.dart
schema_v1.dart
schema_v2.dart
drift_schemas/
drift_schema_v1.json
drift_schema_v2.json
pubspec.yaml
```
The generated migrations implementation and the schema jsons will be generated by drift.
To start writing schemas, create an empty folder named `drift_schemas` in your project.
Of course, you can also choose a different name or use a nested subfolder if you want to.
#### Exporting the schema
To begin, let's create the first schema representation:
```
$ mkdir drift_schemas
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/
```
This instructs the generator to look at the database defined in `lib/database/database.dart` and extract
its schema into the new folder.
After making a change to your database schema, you can run the command again. For instance, let's say we
made a change to our tables and increased the `schemaVersion` to `2`. To dump the new schema, just run the
command again:
```
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/
```
You'll need to run this command every time you change the schema of your database and increment the `schemaVersion`.
Drift will name the files in the folder `drift_schema_vX.json`, where `X` is the current `schemaVersion` of your
database.
If drift is unable to extract the version from your `schemaVersion` getter, provide the full path explicitly:
```
$ dart run drift_dev schema dump lib/database/database.dart drift_schemas/drift_schema_v3.json
```
{% block "blocks/alert" title='<i class="fas fa-lightbulb"></i> Dumping a database' color="success" %}
If, instead of exporting the schema of a database class, you want to export the schema of an existing sqlite3
database file, you can do that as well! `drift_dev schema dump` recognizes a sqlite3 database file as its first
argument and can extract the relevant schema from there.
{% endblock %}
#### Generating test code
After you exported the database schema into a folder, you can generate old versions of your database class
based on those schema files.
For verifications, drift will generate a much smaller database implementation that can only be used to
test migrations.
You can put this test code whereever you want, but it makes sense to put it in a subfolder of `test/`.
If we wanted to write them to `test/generated_migrations/`, we could use
```
$ dart run drift_dev schema generate drift_schemas/ test/generated_migrations/
```
### Writing tests
After that setup, it's finally time to write some tests! For instance, a test could look like this:
```dart
import 'package:my_app/database/database.dart';
import 'package:test/test.dart';
import 'package:drift_dev/api/migrations.dart';
// The generated directory from before.
import 'generated_migrations/schema.dart';
void main() {
late SchemaVerifier verifier;
setUpAll(() {
// GeneratedHelper() was generated by drift, the verifier is an api
// provided by drift_dev.
verifier = SchemaVerifier(GeneratedHelper());
});
test('upgrade from v1 to v2', () async {
// Use startAt(1) to obtain a database connection with all tables
// from the v1 schema.
final connection = await verifier.startAt(1);
final db = MyDatabase.connect(connection);
// Use this to run a migration to v2 and then validate that the
// database has the expected schema.
await verifier.migrateAndValidate(db, 2);
});
}
```
In general, a test looks like this:
1. Use `verifier.startAt()` to obtain a [connection](https://drift.simonbinder.eu/api/drift/databaseconnection-class)
to a database with an initial schema.
This database contains all your tables, indices and triggers from that version, created by using `Migrator.createAll`.
2. Create your application database with that connection - you can forward the `DatabaseConnection` to the
`GeneratedDatabase.connect()` constructor on the parent class for this.
3. Call `verifier.migrateAndValidate(db, version)`. This will initiate a migration towards the target version (here, `2`).
Unlike the database created by `startAt`, this uses the migration logic you wrote for your database.
`migrateAndValidate` will extract all `CREATE` statement from the `sqlite_schema` table and semantically compare them.
If it sees anything unexpected, it will throw a `SchemaMismatch` exception to fail your test.
{% block "blocks/alert" title="Writing testable migrations" %}
To test migrations _towards_ an old schema version (e.g. from `v1` to `v2` if your current version is `v3`),
you're `onUpgrade` handler must be capable of upgrading to a version older than the current `schemaVersion`.
For this, check the `to` parameter of the `onUpgrade` callback to run a different migration if necessary.
{% endblock %}
#### Verifying data integrity
In addition to the changes made in your table structure, its useful to ensure that data that was present before a migration
is still there after it ran.
You can use `schemaAt` to obtain a raw `Database` from the `sqlite3` package in addition to a connection.
This can be used to insert data before a migration. After the migration ran, you can then check that the data is still there.
Note that you can't use the regular database class from you app for this, since its data classes always expect the latest
schema. However, you can instruct drift to generate older snapshots of your data classes and companions for this purpose.
To enable this feature, pass the `--data-classes` and `--companions` command-line arguments to the `drift_dev schema generate`
command:
```
$ dart run drift_dev schema generate --data-classes --companions drift_schemas/ test/generated_migrations/
```
Then, you can import the generated classes with an alias:
```dart
import 'generated_migrations/schema_v1.dart' as v1;
import 'generated_migrations/schema_v2.dart' as v2;
```
This can then be used to manually create and verify data at a specific version:
```dart
void main() {
// ...
test('upgrade from v1 to v2', () async {
final schema = await verifier.schemaAt(1);
// Add some data to the users table, which only has an id column at v1
final oldDb = v1.DatabaseAtV1.connect(schema.newConnection());
await oldDb.into(oldDb.users).insert(const v1.UsersCompanion(id: Value(1)));
await oldDb.close();
// Run the migration and verify that it adds the name column.
final db = Database(schema.newConnection());
await verifier.migrateAndValidate(db, 2);
await db.close();
// Make sure the user is still here
final migratedDb = v2.DatabaseAtV2.connect(schema.newConnection());
final user = await migratedDb.select(migratedDb.users).getSingle();
expect(user.id, 1);
expect(user.name, 'no name'); // default from the migration
await migratedDb.close();
});
}
```
## Verifying a database schema at runtime
Instead (or in addition to) [writing tests](#verifying-migrations) to ensure your migrations work as they should,

View File

@ -1,3 +1,8 @@
## 2.10.0
- Adds the `schema steps` command to `drift_dev`. It generates an API making it
easier to write safe schema migrations ([docs](https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step)).
## 2.9.0
- Forbid `schemaVersion` returning `0`, as this causes issues in the migrator.

View File

@ -0,0 +1,219 @@
/// Defines base classes to generate lightweight tables and views. This library
/// is used by code generated via `drift_dev schema steps` to generate snapshots
/// of every schema version of your database without much overhead.
///
/// For more information on how to use that feature, see
/// https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step
///
/// __Warning:__ This library is not meant to be imported into user-written
/// code, and classes defined in this library are not part of drift's stable
/// API.
library;
import 'package:drift/drift.dart';
/// A snapshot of a database schema at a previous version.
///
/// This class is meant to be extended by generated code.
abstract base class VersionedSchema {
/// The generated database instance, used to create [TableInfo] instances.
final DatabaseConnectionUser database;
/// The [GeneratedDatabase.schemaVersion] at the time this schema was active.
final int version;
/// Default constructor taking the database and the schema version.
VersionedSchema({required this.database, required this.version});
/// All drift schema entities at the time of the set [version].
Iterable<DatabaseSchemaEntity> get entities;
}
/// A drift table implementation that, instead of being generated, is constructed
/// from individual fields
///
/// This allows the code generated for step-by-step migrations to be a lot
/// smaller than the code typically generated by drift. Features like type
/// converters or information about unique/primary keys are not present in these
/// tables.
class VersionedTable extends Table with TableInfo<Table, QueryRow> {
@override
final String entityName;
final String? _alias;
@override
final bool isStrict;
@override
final bool withoutRowId;
@override
final DatabaseConnectionUser attachedDatabase;
@override
final List<GeneratedColumn> $columns;
/// List of columns, represented as a function that returns the generated
/// column when given the resolved table name.
final List<GeneratedColumn Function(String)> _columnFactories;
@override
final List<String> customConstraints;
/// Create a table from the individual fields.
///
/// [columns] is a list of functions returning a [GeneratedColumn] when given
/// the alias (or original name) of this table.
VersionedTable({
required this.entityName,
required this.isStrict,
required this.withoutRowId,
required this.attachedDatabase,
required List<GeneratedColumn Function(String)> columns,
required List<String> tableConstraints,
String? alias,
}) : _columnFactories = columns,
customConstraints = tableConstraints,
$columns = [for (final column in columns) column(alias ?? entityName)],
_alias = alias;
/// Create a table by copying fields from [source] and applying an [alias].
VersionedTable.aliased({
required VersionedTable source,
required String? alias,
}) : entityName = source.entityName,
isStrict = source.isStrict,
withoutRowId = source.withoutRowId,
attachedDatabase = source.attachedDatabase,
customConstraints = source.customConstraints,
_columnFactories = source._columnFactories,
$columns = [
for (final column in source._columnFactories)
column(alias ?? source.entityName)
],
_alias = alias;
@override
String get actualTableName => entityName;
@override
String get aliasedName => _alias ?? entityName;
@override
bool get dontWriteConstraints => true;
@override
QueryRow map(Map<String, dynamic> data, {String? tablePrefix}) {
return QueryRow(data, attachedDatabase);
}
@override
VersionedTable createAlias(String alias) {
return VersionedTable.aliased(source: this, alias: alias);
}
}
/// The version of [VersionedTable] for virtual tables.
class VersionedVirtualTable extends VersionedTable
with VirtualTableInfo<Table, QueryRow> {
@override
final String moduleAndArgs;
/// Create a small virtual table from the individual fields.
VersionedVirtualTable({
required super.entityName,
required super.attachedDatabase,
required super.columns,
required this.moduleAndArgs,
super.alias,
}) : super(
isStrict: false,
withoutRowId: false,
tableConstraints: [],
);
/// Create a virtual table by copying fields from [source] and applying a
/// [alias] to columns.
VersionedVirtualTable.aliased(
{required VersionedVirtualTable source, required String? alias})
: moduleAndArgs = source.moduleAndArgs,
super.aliased(source: source, alias: alias);
@override
VersionedVirtualTable createAlias(String alias) {
return VersionedVirtualTable.aliased(
source: this,
alias: alias,
);
}
}
/// A constructed from individual fields instead of being generated with a
/// dedicated class.
class VersionedView implements ViewInfo<HasResultSet, QueryRow>, HasResultSet {
@override
final String entityName;
final String? _alias;
@override
final String createViewStmt;
@override
final List<GeneratedColumn> $columns;
@override
late final Map<String, GeneratedColumn> columnsByName = {
for (final column in $columns) column.name: column,
};
/// List of columns, represented as a function that returns the generated
/// column when given the resolved table name.
final List<GeneratedColumn Function(String)> _columnFactories;
@override
final DatabaseConnectionUser attachedDatabase;
/// Create a view from the individual fields on [ViewInfo].
VersionedView({
required this.entityName,
required this.attachedDatabase,
required this.createViewStmt,
required List<GeneratedColumn Function(String)> columns,
String? alias,
}) : _columnFactories = columns,
$columns = [for (final column in columns) column(alias ?? entityName)],
_alias = alias;
/// Copy an alias to a [source] view.
VersionedView.aliased({required VersionedView source, required String? alias})
: entityName = source.entityName,
attachedDatabase = source.attachedDatabase,
createViewStmt = source.createViewStmt,
_columnFactories = source._columnFactories,
$columns = [
for (final column in source._columnFactories)
column(alias ?? source.entityName)
],
_alias = alias;
@override
String get aliasedName => _alias ?? entityName;
@override
HasResultSet get asDslTable => this;
@override
VersionedView createAlias(String alias) {
return VersionedView.aliased(source: this, alias: alias);
}
@override
QueryRow map(Map<String, dynamic> data, {String? tablePrefix}) {
return QueryRow(data, attachedDatabase);
}
@override
Query<HasResultSet, dynamic>? get query => null;
@override
Set<String> get readTables => const {};
}

View File

@ -49,14 +49,22 @@ class MigrationStrategy {
/// Runs migrations declared by a [MigrationStrategy].
class Migrator {
final GeneratedDatabase _db;
final VersionedSchema? _fixedVersion;
/// Used internally by drift when opening the database.
Migrator(this._db);
Migrator(this._db, [this._fixedVersion]);
Iterable<DatabaseSchemaEntity> get _allSchemaEntities {
return switch (_fixedVersion) {
null => _db.allSchemaEntities,
var fixed => fixed.entities,
};
}
/// Creates all tables specified for the database, if they don't exist
@Deprecated('Use createAll() instead')
Future<void> createAllTables() async {
for (final table in _db.allTables) {
for (final table in _allSchemaEntities.whereType<TableInfo>()) {
await createTable(table);
}
}
@ -64,7 +72,7 @@ class Migrator {
/// Creates all tables, triggers, views, indexes and everything else defined
/// in the database, if they don't exist.
Future<void> createAll() async {
for (final entity in _db.allSchemaEntities) {
for (final entity in _allSchemaEntities) {
await create(entity);
}
}
@ -94,7 +102,7 @@ class Migrator {
/// a view reads from may also warrant re-creating the view to make sure it's
/// still valid.
Future<void> recreateAllViews() async {
for (final entity in _db.allSchemaEntities) {
for (final entity in _allSchemaEntities) {
if (entity is ViewInfo) {
await drop(entity);
await createView(entity);
@ -119,7 +127,7 @@ class Migrator {
return _issueCustomQuery(context.sql, context.boundVariables);
}
/// Experimental utility method to alter columns of an existing table.
/// Alter columns of an existing tabe.
///
/// Since sqlite does not provide a way to alter the type or constraint of an
/// individual column, one needs to write a fairly complex migration procedure
@ -141,7 +149,6 @@ class Migrator {
///
/// [other alter]: https://www.sqlite.org/lang_altertable.html#otheralter
/// [drift docs]: https://drift.simonbinder.eu/docs/advanced-features/migrations/#complex-migrations
@experimental
Future<void> alterTable(TableMigration migration) async {
final foreignKeysEnabled =
(await _db.customSelect('PRAGMA foreign_keys').getSingle())
@ -474,6 +481,31 @@ class Migrator {
Future<void> _issueCustomQuery(String sql, [List<dynamic>? args]) {
return _db.customStatement(sql, args);
}
/// A helper used by drift internally to implement the [step-by-step](https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step)
/// migration feature.
///
/// This method implements an [OnUpgrade] callback by repeatedly invoking
/// [step] with the current version, assuming that [step] will perform an
/// upgrade from that version to the version returned by the callback.
@experimental
static OnUpgrade stepByStepHelper({
required Future<int> Function(
int currentVersion,
GeneratedDatabase database,
) step,
}) {
return (m, from, to) async {
final database = m._db;
for (var target = from; target < to;) {
final newVersion = await step(target, database);
assert(newVersion > target);
target = newVersion;
}
};
}
}
/// Provides information about whether migrations ran before opening the

View File

@ -6,6 +6,7 @@ import 'dart:collection';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:drift/internal/versioned_schema.dart';
import 'package:drift/src/dsl/dsl.dart';
import 'package:drift/src/runtime/api/options.dart';
import 'package:drift/src/runtime/api/runtime_api.dart';

View File

@ -1,4 +1,5 @@
import 'package:drift/drift.dart';
import 'package:drift/internal/versioned_schema.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
@ -211,6 +212,78 @@ void main() {
// https://github.com/simolus3/drift/discussions/1936
verify(executor.runCustom(argThat(contains('CHECK("foo" < 3)')), []));
});
group('respects schema version', () {
late MockExecutor executor;
late _DefaultDb db;
setUp(() async {
executor = MockExecutor();
db = _DefaultDb(executor);
});
tearDown(() {
db.close();
});
test('in createAll', () async {
final defaultMigrator = db.createMigrator();
await defaultMigrator.createAll();
verifyNever(executor.runCustom(any));
final fixedMigrator =
Migrator(db, _FakeSchemaVersion(database: db, version: 2));
await fixedMigrator.createAll();
verify(executor.runCustom(
'CREATE TABLE IF NOT EXISTS "my_table" ("foo" INTEGER NOT NULL);',
[],
));
verify(executor.runCustom(
'CREATE VIEW my_view AS SELECT 2',
[],
));
});
test('in recreateViews', () async {
final defaultMigrator = db.createMigrator();
await defaultMigrator.recreateAllViews();
verifyNever(executor.runCustom(any));
final fixedMigrator =
Migrator(db, _FakeSchemaVersion(database: db, version: 2));
await fixedMigrator.recreateAllViews();
verify(executor.runCustom(
'CREATE VIEW my_view AS SELECT 2',
[],
));
});
});
}
final class _FakeSchemaVersion extends VersionedSchema {
_FakeSchemaVersion({required super.database, required super.version});
@override
Iterable<DatabaseSchemaEntity> get entities => [
VersionedTable(
entityName: 'my_table',
attachedDatabase: database,
columns: [
(name) => GeneratedColumn<int>('foo', name, false,
type: DriftSqlType.int),
],
tableConstraints: [],
isStrict: false,
withoutRowId: false,
),
VersionedView(
entityName: 'my_view',
attachedDatabase: database,
createViewStmt: 'CREATE VIEW my_view AS SELECT $version',
columns: [],
),
];
}
class _DefaultDb extends GeneratedDatabase {
@ -219,6 +292,9 @@ class _DefaultDb extends GeneratedDatabase {
@override
List<TableInfo<Table, DataClass>> get allTables => [];
@override
Iterable<DatabaseSchemaEntity> get allSchemaEntities => [];
@override
int get schemaVersion => 2;
}

View File

@ -49,7 +49,7 @@ class DriftTable extends DriftElementWithResultSet {
/// when creating the `CREATE TABLE` statement at runtime.
final bool writeDefaultConstraints;
/// When non-null, the generated table class will override the
/// When non-empty, the generated table class will override the
/// `customConstraints` getter in the table class with this value.
final List<String> overrideTableConstraints;
@ -169,21 +169,21 @@ class DriftTable extends DriftElementWithResultSet {
'\$${className}Table';
}
abstract class DriftTableConstraint {}
sealed class DriftTableConstraint {}
class UniqueColumns extends DriftTableConstraint {
final class UniqueColumns extends DriftTableConstraint {
final Set<DriftColumn> uniqueSet;
UniqueColumns(this.uniqueSet);
}
class PrimaryKeyColumns extends DriftTableConstraint {
final class PrimaryKeyColumns extends DriftTableConstraint {
final Set<DriftColumn> primaryKey;
PrimaryKeyColumns(this.primaryKey);
}
class ForeignKeyTable extends DriftTableConstraint {
final class ForeignKeyTable extends DriftTableConstraint {
final List<DriftColumn> localColumns;
final DriftTable otherTable;
@ -214,6 +214,12 @@ class VirtualTableData {
final RecognizedVirtualTableModule? recognized;
/// The module and the arguments in a single string, suitable for `CREATE
/// VIRTUAL TABLE` statements.
String get moduleAndArgs {
return '$module(${moduleArguments.join(', ')})';
}
VirtualTableData(this.module, this.moduleArguments, this.recognized);
}

View File

@ -13,7 +13,7 @@ import 'commands/schema.dart';
import 'logging.dart';
Future run(List<String> args) async {
final cli = MoorCli();
final cli = DriftDevCli();
try {
return await cli.run(args);
} on UsageException catch (e) {
@ -21,14 +21,14 @@ Future run(List<String> args) async {
}
}
class MoorCli {
class DriftDevCli {
Logger get logger => Logger.root;
late final CommandRunner _runner;
late final MoorProject project;
bool verbose = false;
MoorCli() {
DriftDevCli() {
_runner = CommandRunner(
'dart run drift_dev',
'CLI utilities for the drift package, currently in an experimental state.',
@ -72,7 +72,7 @@ class MoorCli {
}
abstract class MoorCommand extends Command {
final MoorCli cli;
final DriftDevCli cli;
MoorCommand(this.cli);
}

View File

@ -3,7 +3,7 @@ import 'dart:io';
import '../cli.dart';
class AnalyzeCommand extends MoorCommand {
AnalyzeCommand(MoorCli cli) : super(cli);
AnalyzeCommand(DriftDevCli cli) : super(cli);
@override
String get description => 'Analyze and lint drift database code';

View File

@ -7,7 +7,7 @@ import '../../analysis/results/results.dart';
import '../cli.dart';
class IdentifyDatabases extends MoorCommand {
IdentifyDatabases(MoorCli cli) : super(cli);
IdentifyDatabases(DriftDevCli cli) : super(cli);
@override
String get description =>

View File

@ -23,7 +23,7 @@ class MigrateCommand extends MoorCommand {
late final AnalysisContext context;
MigrateCommand(MoorCli cli) : super(cli);
MigrateCommand(DriftDevCli cli) : super(cli);
@override
String get description => 'Migrate a project from moor to drift';

View File

@ -1,9 +1,16 @@
import 'package:args/command_runner.dart';
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as p;
import '../../analysis/results/results.dart';
import '../../services/schema/schema_files.dart';
import 'schema/dump.dart';
import 'schema/generate_utils.dart';
import '../cli.dart';
import 'schema/steps.dart';
class SchemaCommand extends Command {
@override
@ -12,8 +19,37 @@ class SchemaCommand extends Command {
@override
String get name => 'schema';
SchemaCommand(MoorCli cli) {
SchemaCommand(DriftDevCli cli) {
addSubcommand(DumpSchemaCommand(cli));
addSubcommand(GenerateUtilsCommand(cli));
addSubcommand(WriteVersions(cli));
}
}
class ExportedSchema {
final List<DriftElement> schema;
final Map<String, Object?> options;
ExportedSchema(this.schema, this.options);
}
final _filenames = RegExp(r'(?:moor|drift)_schema_v(\d+)\.json');
Future<Map<int, ExportedSchema>> parseSchema(Directory directory) async {
final results = <int, ExportedSchema>{};
await for (final entity in directory.list()) {
final basename = p.basename(entity.path);
final match = _filenames.firstMatch(basename);
if (match == null || entity is! File) continue;
final version = int.parse(match.group(1)!);
final rawData = json.decode(await entity.readAsString());
final schema = SchemaReader.readJson(rawData as Map<String, dynamic>);
results[version] = ExportedSchema(schema.entities.toList(), schema.options);
}
return results;
}

View File

@ -24,7 +24,7 @@ class DumpSchemaCommand extends Command {
return '${runner!.executableName} schema dump [arguments] <input> <output>';
}
final MoorCli cli;
final DriftDevCli cli;
DumpSchemaCommand(this.cli) {
argParser.addSeparator("It's recommended to run this commend from the "

View File

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
@ -13,9 +12,10 @@ import '../../../writer/database_writer.dart';
import '../../../writer/import_manager.dart';
import '../../../writer/writer.dart';
import '../../cli.dart';
import '../schema.dart';
class GenerateUtilsCommand extends Command {
final MoorCli cli;
final DriftDevCli cli;
GenerateUtilsCommand(this.cli) {
argParser.addFlag(
@ -61,7 +61,7 @@ class GenerateUtilsCommand extends Command {
await outputDir.create();
}
final schema = await _parseSchema(inputDir);
final schema = await parseSchema(inputDir);
for (final versionAndEntities in schema.entries) {
final version = versionAndEntities.key;
final entities = versionAndEntities.value;
@ -81,30 +81,10 @@ class GenerateUtilsCommand extends Command {
'Wrote ${schema.length + 1} files into ${p.relative(outputDir.path)}');
}
Future<Map<int, _ExportedSchema>> _parseSchema(Directory directory) async {
final results = <int, _ExportedSchema>{};
await for (final entity in directory.list()) {
final basename = p.basename(entity.path);
final match = _filenames.firstMatch(basename);
if (match == null || entity is! File) continue;
final version = int.parse(match.group(1)!);
final rawData = json.decode(await entity.readAsString());
final schema = SchemaReader.readJson(rawData as Map<String, dynamic>);
results[version] =
_ExportedSchema(schema.entities.toList(), schema.options);
}
return results;
}
Future<void> _writeSchemaFile(
Directory output,
int version,
_ExportedSchema schema,
ExportedSchema schema,
bool dataClasses,
bool companions,
) {
@ -184,15 +164,7 @@ class GenerateUtilsCommand extends Command {
String _filenameForVersion(int version) => 'schema_v$version.dart';
static final _filenames = RegExp(r'(?:moor|drift)_schema_v(\d+)\.json');
static final _dartfmt = DartFormatter();
static const _prefix = '// GENERATED CODE, DO NOT EDIT BY HAND.\n'
'// ignore_for_file: type=lint';
}
class _ExportedSchema {
final List<DriftElement> schema;
final Map<String, Object?> options;
_ExportedSchema(this.schema, this.options);
}

View File

@ -0,0 +1,81 @@
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:collection/collection.dart';
import 'package:dart_style/dart_style.dart';
import '../../../analysis/options.dart';
import '../../../analysis/results/element.dart';
import '../../../writer/import_manager.dart';
import '../../../writer/schema_version_writer.dart';
import '../../../writer/writer.dart';
import '../../cli.dart';
import '../schema.dart';
class WriteVersions extends Command {
final DriftDevCli cli;
WriteVersions(this.cli);
@override
String get name => 'steps';
@override
String get description =>
'Write a Dart file helping with incremental migrations between schema versions.';
@override
String get invocation {
return '${runner!.executableName} schema steps <schema directory> <output file>';
}
@override
Future<void> run() async {
final rest = argResults!.rest;
if (rest.length != 2) {
usageException('Expected input and output directories');
}
final inputDirectory = Directory(rest[0]);
final outputFile = File(rest[1]);
final outputDirectory = outputFile.parent;
if (!await inputDirectory.exists()) {
cli.exit('The provided input directory does not exist.');
}
if (!await outputDirectory.exists()) {
await outputDirectory.create();
}
final imports = LibraryImportManager();
final writer = Writer(
const DriftOptions.defaults(),
generationOptions: GenerationOptions(imports: imports),
);
imports.linkToWriter(writer);
final schema = await parseSchema(inputDirectory);
final byVersion = [
for (final MapEntry(key: version, value: schema) in schema.entries)
SchemaVersion(
version,
schema.schema.whereType<DriftSchemaElement>().toList(),
schema.options,
),
];
byVersion.sortBy<num>((s) => s.version);
writer.leaf().write("import 'package:drift/drift.dart';");
SchemaVersionWriter(byVersion, writer.child()).write();
var code = writer.writeGenerated();
try {
code = DartFormatter().format(code);
} on FormatterException {
// Ignore. Probably a bug in drift_dev, the user will notice.
}
await outputFile.writeAsString(code);
}
}

View File

@ -0,0 +1,398 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show DriftSqlType;
import 'package:sqlparser/sqlparser.dart' as sql;
import 'package:sqlparser/utils/node_to_text.dart';
import '../analysis/results/results.dart';
import '../utils/string_escaper.dart';
import 'tables/table_writer.dart';
import 'writer.dart';
class SchemaVersion {
final int version;
final List<DriftSchemaElement> schema;
final Map<String, Object?> options;
SchemaVersion(this.version, this.schema, this.options);
}
enum _ResultSetKind {
table,
virtualTable,
view,
}
final class _TableShape {
final _ResultSetKind kind;
// Map from Dart getter names to column names in SQL and the SQL type.
final Map<String, (String, DriftSqlType)> columnTypes;
_TableShape(this.kind, this.columnTypes);
@override
int get hashCode => Object.hash(kind, _equality.hash(columnTypes));
@override
bool operator ==(Object other) {
return other is _TableShape &&
other.kind == kind &&
_equality.equals(other.columnTypes, columnTypes);
}
static const _equality = MapEquality<String, (String, DriftSqlType)>();
static Map<String, (String, DriftSqlType)> columnsFrom(
DriftElementWithResultSet e) {
return {
for (final column in e.columns)
column.nameInDart: (column.nameInSql, column.sqlType),
};
}
}
/// A writer that writes schema code for all schema versions known to us.
///
/// While other tools to generate code for a specific schema version exists
/// (we use it to generate test code), the generated code is very large since
/// it can contain data classes and all information known to drift.
/// Code generated by this writer is optimized to be compact by hiding table
/// metadata not strictly necessary for migrations and by re-using column
/// definitions where possible.
class SchemaVersionWriter {
static final Uri _schemaLibrary =
Uri.parse('package:drift/internal/versioned_schema.dart');
/// All schema versions, sorted by [SchemaVersion.version].
final List<SchemaVersion> versions;
final Scope libraryScope;
final Map<String, String> _columnCodeToFactory = {};
final Map<_TableShape, String> _shapes = {};
SchemaVersionWriter(this.versions, this.libraryScope) {
assert(versions.isSortedBy<num>((element) => element.version));
}
/// Since not every column changes in every schema version, we prefer to re-use
/// columns with an identical definition across tables and schema versions.
///
/// We do this by generating a method constructing the column which can be
/// called in different places. This method looks up or creates a method for
/// the given [column], returning it if doesn't exist.
String _referenceColumn(DriftColumn column) {
final text = libraryScope.leaf();
final (type, code) = TableOrViewWriter.instantiateColumn(column, text);
return _columnCodeToFactory.putIfAbsent(code, () {
final methodName = '_column_${_columnCodeToFactory.length}';
text.writeln('$type $methodName(String aliasedName) => $code;');
return methodName;
});
}
void _writeColumnsArgument(List<DriftColumn> columns, TextEmitter writer) {
writer.write('columns: [');
for (final column in columns) {
writer
..write(_referenceColumn(column))
..write(',');
}
writer.write('],');
}
/// Finds a class to use for [resultSet].
///
/// When only minor details like column or table constraints change, we don't
/// want to introduce a new class. The interface of a class is only determined
/// by its kind (since we need to subclass from VersionedTable,
/// VersionedVirtualTable or VersionedView) and its public getters used to
/// access columns.
///
/// This looks up a suitable class for the existing [resultSet] or creates a
/// new one, returning its name.
String _shapeClass(DriftElementWithResultSet resultSet) {
final (kind, superclass) = switch (resultSet) {
DriftTable(virtualTableData: null) => (
_ResultSetKind.table,
'VersionedTable'
),
DriftTable() => (_ResultSetKind.virtualTable, 'VersionedVirtualTable'),
DriftView() => (_ResultSetKind.view, 'VersionedView'),
_ => throw ArgumentError.value(resultSet, 'resultSet', 'Unknown type'),
};
final shape = _TableShape(kind, _TableShape.columnsFrom(resultSet));
return _shapes.putIfAbsent(shape, () {
final className = 'Shape${_shapes.length}';
final classWriter = libraryScope.leaf();
classWriter
..write('class $className extends ')
..writeUriRef(_schemaLibrary, superclass)
..writeln('{')
..writeln(
'$className({required super.source, required super.alias}) : super.aliased();');
for (final MapEntry(key: getterName, value: (sqlName, type))
in shape.columnTypes.entries) {
final columnType = AnnotatedDartCode([dartTypeNames[type]!]);
classWriter
..writeDriftRef('GeneratedColumn<')
..writeDart(columnType)
..write('> get ')
..write(getterName)
..write(' => columnsByName[${asDartLiteral(sqlName)}]! as ')
..writeDriftRef('GeneratedColumn<')
..writeDart(columnType)
..writeln('>;');
}
classWriter.writeln('}');
return className;
});
}
String _writeWithResultSet(
DriftElementWithResultSet entity, TextEmitter writer) {
final getterName = entity.dbGetterName;
final shape = _shapeClass(entity);
writer
..write('late final $shape $getterName = ')
..write('$shape(source: ');
switch (entity) {
case DriftTable():
if (entity.isVirtual) {
final info = entity.virtualTableData!;
writer
..writeUriRef(_schemaLibrary, 'VersionedVirtualTable(')
..write('entityName: ${asDartLiteral(entity.schemaName)},')
..write('moduleAndArgs: ${asDartLiteral(info.moduleAndArgs)},');
} else {
final tableConstraints = <String>[];
if (entity.writeDefaultConstraints) {
// We don't override primaryKey and uniqueKey in generated table
// classes to keep the code shorter. The migrator would use those
// getters to generate SQL at runtime, which means that this burden
// now falls onto the generator.
for (final constraint in entity.tableConstraints) {
final astNode = switch (constraint) {
PrimaryKeyColumns(primaryKey: var columns) => sql.KeyClause(
null,
isPrimaryKey: true,
columns: [
for (final column in columns)
sql.IndexedColumn(
sql.Reference(columnName: column.nameInSql))
],
),
UniqueColumns(uniqueSet: var columns) => sql.KeyClause(
null,
isPrimaryKey: false,
columns: [
for (final column in columns)
sql.IndexedColumn(
sql.Reference(columnName: column.nameInSql))
],
),
ForeignKeyTable() => sql.ForeignKeyTableConstraint(
null,
columns: [
for (final column in constraint.localColumns)
sql.Reference(columnName: column.nameInSql)
],
clause: sql.ForeignKeyClause(
foreignTable:
sql.TableReference(constraint.otherTable.schemaName),
columnNames: [
for (final column in constraint.otherColumns)
sql.Reference(columnName: column.nameInSql)
],
onUpdate: constraint.onUpdate,
onDelete: constraint.onDelete,
),
),
};
tableConstraints.add(astNode.toSql());
}
}
tableConstraints.addAll(entity.overrideTableConstraints.toList());
writer
..writeUriRef(_schemaLibrary, 'VersionedTable(')
..write('entityName: ${asDartLiteral(entity.schemaName)},')
..write('withoutRowId: ${entity.withoutRowId},')
..write('isStrict: ${entity.strict},')
..write('tableConstraints: [');
for (final constraint in tableConstraints) {
writer
..write(asDartLiteral(constraint))
..write(',');
}
writer.write('],');
}
break;
case DriftView():
final source = entity.source as SqlViewSource;
writer
..writeUriRef(_schemaLibrary, 'VersionedView(')
..write('entityName: ${asDartLiteral(entity.schemaName)},')
..write(
'createViewStmt: ${asDartLiteral(source.sqlCreateViewStmt)},');
break;
}
_writeColumnsArgument(entity.columns, writer);
writer.write('attachedDatabase: database,');
writer.write('), alias: null)');
return getterName!;
}
String _writeEntity({
required DriftSchemaElement element,
required TextEmitter definition,
}) {
String name;
if (element is DriftElementWithResultSet) {
name = _writeWithResultSet(element, definition);
} else if (element is DriftIndex) {
name = element.dbGetterName;
final index = definition.drift('Index');
definition
..write('final $index $name = $index(')
..write(asDartLiteral(element.schemaName))
..write(',')
..write(asDartLiteral(element.createStmt))
..write(')');
} else if (element is DriftTrigger) {
name = element.dbGetterName;
final trigger = definition.drift('Trigger');
definition
..write('final $trigger $name = $trigger(')
..write(asDartLiteral(element.createStmt))
..write(',')
..write(asDartLiteral(element.schemaName))
..write(')');
} else {
throw ArgumentError('Unhandled element type $element');
}
definition.write(';');
return name;
}
void write() {
libraryScope.leaf()
..writeln('// ignore_for_file: type=lint,unused_import')
..writeln('// GENERATED BY drift_dev, DO NOT MODIFY.');
// There is no need to generate schema classes for the first version, we
// only need them for versions targeted by migrations.
for (final version in versions.skip(1)) {
final versionNo = version.version;
final versionClass = '_S$versionNo';
final versionScope = libraryScope.child();
// Write an _S<x> class for each schema version x.
versionScope.leaf()
..write('final class $versionClass extends ')
..writeUriRef(_schemaLibrary, 'VersionedSchema')
..writeln('{')
..writeln('$versionClass({required super.database}): '
'super(version: $versionNo);');
// Override the allEntities getters by VersionedSchema
final allEntitiesWriter = versionScope.leaf()
..write('@override late final ')
..writeUriRef(AnnotatedDartCode.dartCore, 'List')
..write('<')
..writeDriftRef('DatabaseSchemaEntity')
..write('> entities = [');
for (final entity in version.schema) {
// Creata field for the entity and include it in the list
final fieldName =
_writeEntity(element: entity, definition: versionScope.leaf());
allEntitiesWriter.write('$fieldName,');
}
allEntitiesWriter.write('];');
versionScope.leaf().writeln('}');
}
// Write a stepByStep migration function that takes a callback doing a step
// for each schema to the next. We supply a special migrator that only
// considers entities from that version, as well as a typed reference to the
// _S<x> class used to lookup elements.
final stepByStep = libraryScope.leaf()
..writeDriftRef('OnUpgrade')
..write(' stepByStep({');
for (final (current, next) in versions.withNext) {
stepByStep
..write('required Future<void> Function(')
..writeDriftRef('Migrator')
..write(' m, _S${next.version} schema)')
..writeln('from${current.version}To${next.version},');
}
stepByStep
..writeln('}) {')
..write('return ')
..writeDriftRef('Migrator')
..writeln('.stepByStepHelper(step: (currentVersion, database) async {')
..writeln('switch (currentVersion) {');
for (final (current, next) in versions.withNext) {
stepByStep
..writeln('case ${current.version}:')
..write('final schema = _S${next.version}(database: database);')
..write('final migrator = ')
..writeDriftRef('Migrator')
..writeln('(database, schema);')
..writeln(
'await from${current.version}To${next.version}(migrator, schema);')
..writeln('return ${next.version};');
}
stepByStep
..writeln(
r"default: throw ArgumentError.value('Unknown migration from $currentVersion');")
..writeln('}') // End of switch
..writeln('}') // End of stepByStepHelper function
..writeln(');') // End of stepByStepHelper call
..writeln('}'); // End of method
}
}
extension<T> on Iterable<T> {
Iterable<(T, T)> get withNext sync* {
final iterator = this.iterator;
if (!iterator.moveNext()) return;
var a = iterator.current;
while (iterator.moveNext()) {
var b = iterator.current;
yield (a, b);
a = b;
}
}
}

View File

@ -16,140 +16,24 @@ abstract class TableOrViewWriter {
StringBuffer get buffer => emitter.buffer;
void writeColumnGetter(DriftColumn column, bool isOverride) {
final isNullable = column.nullable;
final additionalParams = <String, String>{};
final expressionBuffer = StringBuffer();
final constraints = defaultConstraints(column);
for (final constraint in column.constraints) {
if (constraint is LimitingTextLength) {
final buffer =
StringBuffer(emitter.drift('GeneratedColumn.checkTextLength('));
if (constraint.minLength != null) {
buffer.write('minTextLength: ${constraint.minLength},');
}
if (constraint.maxLength != null) {
buffer.write('maxTextLength: ${constraint.maxLength}');
}
buffer.write(')');
additionalParams['additionalChecks'] = buffer.toString();
}
if (constraint is DartCheckExpression) {
final dartCheck = emitter.dartCode(constraint.dartExpression);
additionalParams['check'] = '() => $dartCheck';
}
if (constraint is ColumnGeneratedAs) {
final dartCode = emitter.dartCode(constraint.dartExpression);
additionalParams['generatedAs'] =
'${emitter.drift('GeneratedAs')}($dartCode, ${constraint.stored})';
}
if (constraint is PrimaryKeyColumn && constraint.isAutoIncrement) {
additionalParams['hasAutoIncrement'] = 'true';
}
}
additionalParams['type'] = emitter.drift(column.sqlType.toString());
bool? isRequiredForInsert;
if (tableOrView is DriftTable) {
additionalParams['requiredDuringInsert'] = (tableOrView as DriftTable)
.isColumnRequiredForInsert(column)
.toString();
isRequiredForInsert =
(tableOrView as DriftTable).isColumnRequiredForInsert(column);
}
if (column.customConstraints != null) {
additionalParams['\$customConstraints'] =
asDartLiteral(column.customConstraints!);
} else if (constraints.values.any((constraint) => constraint.isNotEmpty)) {
// Use the default constraints supported by drift
if (constraints.values.any(
(value) => value != constraints.values.first,
)) {
// One or more constraints are different depending on dialect, generate
// per-dialect constraints
final literalEntries = [
for (final entry in constraints.entries)
'${emitter.drift('SqlDialect.${entry.key.name}')}: ${asDartLiteral(entry.value)},',
];
additionalParams['defaultConstraints'] =
'${emitter.drift('GeneratedColumn.constraintsDependsOnDialect')}({${literalEntries.join('\n')}})';
} else {
// Constraints are the same regardless of dialect, only generate one set
// of them
final constraint = asDartLiteral(constraints.values.first);
additionalParams['defaultConstraints'] =
'${emitter.drift('GeneratedColumn.constraintIsAlways')}($constraint)';
}
}
if (column.defaultArgument != null) {
additionalParams['defaultValue'] =
emitter.dartCode(column.defaultArgument!);
}
if (column.clientDefaultCode != null) {
additionalParams['clientDefault'] =
emitter.dartCode(column.clientDefaultCode!);
}
final innerType = emitter.innerColumnType(column);
var type =
'${emitter.drift('GeneratedColumn')}<${emitter.dartCode(innerType)}>';
expressionBuffer
..write(type)
..write(
'(${asDartLiteral(column.nameInSql)}, aliasedName, $isNullable, ');
var first = true;
additionalParams.forEach((name, value) {
if (!first) {
expressionBuffer.write(', ');
} else {
first = false;
}
expressionBuffer
..write(name)
..write(': ')
..write(value);
});
expressionBuffer.write(')');
final converter = column.typeConverter;
if (converter != null) {
// Generate a GeneratedColumnWithTypeConverter instance, as it has
// additional methods to check for equality against a mapped value.
final mappedType = emitter.dartCode(emitter.writer.dartType(column));
final converterCode = emitter.dartCode(emitter.writer
.readConverter(converter, forNullable: column.nullable));
type = '${emitter.drift('GeneratedColumnWithTypeConverter')}'
'<$mappedType, ${emitter.dartCode(innerType)}>';
expressionBuffer
..write('.withConverter<')
..write(mappedType)
..write('>(')
..write(converterCode)
..write(')');
}
final (type, expression) = instantiateColumn(
column,
emitter,
isRequiredForInsert: isRequiredForInsert,
);
writeMemoizedGetter(
buffer: buffer,
getterName: column.nameInDart,
returnType: type,
code: expressionBuffer.toString(),
code: expression,
hasOverride: isOverride,
);
}
@ -276,6 +160,143 @@ abstract class TableOrViewWriter {
buffer.write(
'@override\n${tableOrView.entityInfoName} get asDslTable => this;\n');
}
/// Returns the Dart type and the Dart expression creating a `GeneratedColumn`
/// instance in drift for the givne [column].
static (String, String) instantiateColumn(
DriftColumn column,
TextEmitter emitter, {
bool? isRequiredForInsert,
}) {
final isNullable = column.nullable;
final additionalParams = <String, String>{};
final expressionBuffer = StringBuffer();
final constraints = defaultConstraints(column);
for (final constraint in column.constraints) {
if (constraint is LimitingTextLength) {
final buffer =
StringBuffer(emitter.drift('GeneratedColumn.checkTextLength('));
if (constraint.minLength != null) {
buffer.write('minTextLength: ${constraint.minLength},');
}
if (constraint.maxLength != null) {
buffer.write('maxTextLength: ${constraint.maxLength}');
}
buffer.write(')');
additionalParams['additionalChecks'] = buffer.toString();
}
if (constraint is DartCheckExpression) {
final dartCheck = emitter.dartCode(constraint.dartExpression);
additionalParams['check'] = '() => $dartCheck';
}
if (constraint is ColumnGeneratedAs) {
final dartCode = emitter.dartCode(constraint.dartExpression);
additionalParams['generatedAs'] =
'${emitter.drift('GeneratedAs')}($dartCode, ${constraint.stored})';
}
if (constraint is PrimaryKeyColumn && constraint.isAutoIncrement) {
additionalParams['hasAutoIncrement'] = 'true';
}
}
additionalParams['type'] = emitter.drift(column.sqlType.toString());
if (isRequiredForInsert != null) {
additionalParams['requiredDuringInsert'] = isRequiredForInsert.toString();
}
if (column.customConstraints != null) {
additionalParams['\$customConstraints'] =
asDartLiteral(column.customConstraints!);
} else if (constraints.values.any((constraint) => constraint.isNotEmpty)) {
// Use the default constraints supported by drift
if (constraints.values.any(
(value) => value != constraints.values.first,
)) {
// One or more constraints are different depending on dialect, generate
// per-dialect constraints
final literalEntries = [
for (final entry in constraints.entries)
'${emitter.drift('SqlDialect.${entry.key.name}')}: ${asDartLiteral(entry.value)},',
];
additionalParams['defaultConstraints'] =
'${emitter.drift('GeneratedColumn.constraintsDependsOnDialect')}({${literalEntries.join('\n')}})';
} else {
// Constraints are the same regardless of dialect, only generate one set
// of them
final constraint = asDartLiteral(constraints.values.first);
additionalParams['defaultConstraints'] =
'${emitter.drift('GeneratedColumn.constraintIsAlways')}($constraint)';
}
}
if (column.defaultArgument != null) {
additionalParams['defaultValue'] =
emitter.dartCode(column.defaultArgument!);
}
if (column.clientDefaultCode != null) {
additionalParams['clientDefault'] =
emitter.dartCode(column.clientDefaultCode!);
}
final innerType = emitter.innerColumnType(column);
var type =
'${emitter.drift('GeneratedColumn')}<${emitter.dartCode(innerType)}>';
expressionBuffer
..write(type)
..write(
'(${asDartLiteral(column.nameInSql)}, aliasedName, $isNullable, ');
var first = true;
additionalParams.forEach((name, value) {
if (!first) {
expressionBuffer.write(', ');
} else {
first = false;
}
expressionBuffer
..write(name)
..write(': ')
..write(value);
});
expressionBuffer.write(')');
final converter = column.typeConverter;
if (converter != null) {
// Generate a GeneratedColumnWithTypeConverter instance, as it has
// additional methods to check for equality against a mapped value.
final mappedType = emitter.dartCode(emitter.writer.dartType(column));
final converterCode = emitter.dartCode(emitter.writer
.readConverter(converter, forNullable: column.nullable));
type = '${emitter.drift('GeneratedColumnWithTypeConverter')}'
'<$mappedType, ${emitter.dartCode(innerType)}>';
expressionBuffer
..write('.withConverter<')
..write(mappedType)
..write('>(')
..write(converterCode)
..write(')');
}
return (type, expressionBuffer.toString());
}
}
class TableWriter extends TableOrViewWriter {
@ -577,8 +598,7 @@ class TableWriter extends TableOrViewWriter {
if (table.isVirtual) {
final stmt = table.virtualTableData!;
final moduleAndArgs =
asDartLiteral('${stmt.module}(${stmt.moduleArguments.join(', ')})');
final moduleAndArgs = asDartLiteral(stmt.moduleAndArgs);
buffer
..write('@override\n')
..write('String get moduleAndArgs => $moduleAndArgs;\n');

View File

@ -11,7 +11,7 @@ topics:
- database
environment:
sdk: '>=2.17.0 <4.0.0'
sdk: '>=3.0.0 <4.0.0'
dependencies:
charcode: ^1.2.0

View File

@ -13,7 +13,7 @@ class TestDriftProject {
Future<void> runDriftCli(Iterable<String> args) {
return IOOverrides.runZoned(
() => MoorCli().run(args),
() => DriftDevCli().run(args),
getCurrentDirectory: () => root,
);
}

View File

@ -31,7 +31,7 @@ Drift databases don't depend on platform-channels or Flutter-specific features
by default. This means that they can easily be used in unit tests.
One such test is in `test/database_test.dart`
#### Testing migrations
### Migration
After changing the structure of your database schema, for instance by adding
new tables or altering columns, you need to write a migration to ensure that
@ -57,4 +57,11 @@ you can use to write unit tests for schema migrations:
dart run drift_dev schema generate drift_schemas/ test/generated_migrations/
```
To make migrations easier, this command updates the `lib/database/schema_versions.dart`
file containing snapshots of older database schema:
```
dart run drift_dev schema steps drift_schemas/ lib/database/schema_versions.dart
```
An example for a schema test is in `test/migration_test.dart`.

View File

@ -4,6 +4,10 @@ import 'package:riverpod/riverpod.dart';
import 'connection/connection.dart' as impl;
import 'tables.dart';
// Manually generated by `drift_dev schema steps` - this file makes writing
// migrations easier. See this for details:
// https://drift.simonbinder.eu/docs/advanced-features/migrations/#step-by-step
import 'schema_versions.dart';
// Generated by drift_dev when running `build_runner build`
part 'database.g.dart';
@ -20,26 +24,22 @@ class AppDatabase extends _$AppDatabase {
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: ((m, from, to) async {
for (var step = from + 1; step <= to; step++) {
switch (step) {
case 2:
onUpgrade: stepByStep(
from1To2: (m, schema) async {
// The todoEntries.dueDate column was added in version 2.
await m.addColumn(todoEntries, todoEntries.dueDate);
break;
case 3:
await m.addColumn(schema.todoEntries, schema.todoEntries.dueDate);
},
from2To3: (m, schema) async {
// New triggers were added in version 3:
await m.create(todosDelete);
await m.create(todosUpdate);
await m.create(schema.todosDelete);
await m.create(schema.todosUpdate);
// Also, the `REFERENCES` constraint was added to
// [TodoEntries.category]. Run a table migration to rebuild all
// column constraints without loosing data.
await m.alterTable(TableMigration(todoEntries));
break;
}
}
}),
await m.alterTable(TableMigration(schema.todoEntries));
},
),
beforeOpen: (details) async {
// Make sure that foreign keys are enabled
await customStatement('PRAGMA foreign_keys = ON');

View File

@ -0,0 +1,202 @@
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
// GENERATED BY drift_dev, DO NOT MODIFY.
final class _S2 extends i0.VersionedSchema {
_S2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
categories,
todoEntries,
textEntries,
todosInsert,
];
late final Shape0 categories = Shape0(
source: i0.VersionedTable(
entityName: 'categories',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 todoEntries = Shape1(
source: i0.VersionedTable(
entityName: 'todo_entries',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 textEntries = Shape2(
source: i0.VersionedVirtualTable(
entityName: 'text_entries',
moduleAndArgs:
'fts5(description, content=todo_entries, content_rowid=id)',
columns: [
_column_6,
],
attachedDatabase: database,
),
alias: null);
final i1.Trigger todosInsert = i1.Trigger(
'CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN\n INSERT INTO text_entries(rowid, description) VALUES (new.id, new.description);\nEND;',
'todos_insert');
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get color =>
columnsByName['color']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('name', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<int> _column_2(String aliasedName) =>
i1.GeneratedColumn<int>('color', aliasedName, false,
type: i1.DriftSqlType.int);
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get description =>
columnsByName['description']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get category =>
columnsByName['category']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get dueDate =>
columnsByName['due_date']! as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>('description', aliasedName, false,
type: i1.DriftSqlType.string);
i1.GeneratedColumn<int> _column_4(String aliasedName) =>
i1.GeneratedColumn<int>('category', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<DateTime> _column_5(String aliasedName) =>
i1.GeneratedColumn<DateTime>('due_date', aliasedName, true,
type: i1.DriftSqlType.dateTime);
class Shape2 extends i0.VersionedVirtualTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get description =>
columnsByName['description']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_6(String aliasedName) =>
i1.GeneratedColumn<String>('description', aliasedName, false,
type: i1.DriftSqlType.string, $customConstraints: '');
final class _S3 extends i0.VersionedSchema {
_S3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
categories,
todoEntries,
textEntries,
todosInsert,
todosDelete,
todosUpdate,
];
late final Shape0 categories = Shape0(
source: i0.VersionedTable(
entityName: 'categories',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 todoEntries = Shape1(
source: i0.VersionedTable(
entityName: 'todo_entries',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_3,
_column_7,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 textEntries = Shape2(
source: i0.VersionedVirtualTable(
entityName: 'text_entries',
moduleAndArgs:
'fts5(description, content=todo_entries, content_rowid=id)',
columns: [
_column_6,
],
attachedDatabase: database,
),
alias: null);
final i1.Trigger todosInsert = i1.Trigger(
'CREATE TRIGGER todos_insert AFTER INSERT ON todo_entries BEGIN INSERT INTO text_entries ("rowid", description) VALUES (new.id, new.description);END',
'todos_insert');
final i1.Trigger todosDelete = i1.Trigger(
'CREATE TRIGGER todos_delete AFTER DELETE ON todo_entries BEGIN INSERT INTO text_entries (text_entries, "rowid", description) VALUES (\'delete\', old.id, old.description);END',
'todos_delete');
final i1.Trigger todosUpdate = i1.Trigger(
'CREATE TRIGGER todos_update AFTER UPDATE ON todo_entries BEGIN INSERT INTO text_entries (text_entries, "rowid", description) VALUES (\'delete\', new.id, new.description);INSERT INTO text_entries ("rowid", description) VALUES (new.id, new.description);END',
'todos_update');
}
i1.GeneratedColumn<int> _column_7(String aliasedName) =>
i1.GeneratedColumn<int>('category', aliasedName, true,
type: i1.DriftSqlType.int,
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'REFERENCES categories (id)'));
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, _S2 schema) from1To2,
required Future<void> Function(i1.Migrator m, _S3 schema) from2To3,
}) {
return i1.Migrator.stepByStepHelper(step: (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = _S2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
case 2:
final schema = _S3(database: database);
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
});
}

View File

@ -5,7 +5,7 @@ publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.16.1 <3.0.0"
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
@ -15,9 +15,8 @@ dependencies:
file_picker: ^5.2.5
flutter_colorpicker: ^1.0.3
flutter_riverpod: ^2.3.0
go_router: ^6.2.0
go_router: ^9.0.0
intl: ^0.18.0
http: ^0.13.4 # used to load sqlite3 wasm files on the web
sqlite3_flutter_libs: ^0.5.5
sqlite3: ^2.0.0
path_provider: ^2.0.9
@ -34,7 +33,7 @@ dev_dependencies:
# web workers for drift AND want to compile those web workers through `build_runner`.
# Either way, using this package with Flutter requires a delicate setup described in
# `build.yaml`.
build_web_compilers: ^3.2.2
build_web_compilers: ^4.0.0
build: ^2.2.1
flutter:

View File

@ -20,9 +20,9 @@ Run
dart run drift_dev schema generate drift_migrations/ test/generated/ --data-classes --companions
```
We're also using test code inside `lib/` to run migrations with older definitions of tables.
This isn't required for all migrations, but can be useful in some cases.
Since we're using the step-by-step generator to make writing migrations easier, this command
is used to generate a helper file in `lib/`:
```
dart run drift_dev schema generate drift_migrations/ lib/src/generated
dart run drift_dev schema steps drift_migrations/ lib/src/versions.dart
```

View File

@ -2,9 +2,7 @@ import 'package:drift/drift.dart';
import 'package:drift_dev/api/migrations.dart';
import 'tables.dart';
import 'src/generated/schema_v2.dart' as v2;
import 'src/generated/schema_v4.dart' as v4;
import 'src/generated/schema_v8.dart' as v8;
import 'src/versions.dart';
part 'database.g.dart';
@ -20,59 +18,7 @@ class Database extends _$Database {
@override
MigrationStrategy get migration {
return MigrationStrategy(
onUpgrade: (m, before, now) async {
for (var target = before + 1; target <= now; target++) {
switch (target) {
case 2:
// Migration from 1 to 2: Add name column in users. Use "no name"
// as a default value.
final usersAtV2 = v2.Users(this);
await m.alterTable(
TableMigration(
usersAtV2,
columnTransformer: {
users.name: const Constant<String>('no name'),
},
newColumns: [usersAtV2.name],
),
);
break;
case 3:
// Migration from 2 to 3: We added the groups table
await m.createTable(groups);
break;
case 4:
// Migration from 3 to 4: users.name now has a default value
// No need to transform any data, just re-create the table
final usersAtV4 = v4.Users(this);
await m.alterTable(TableMigration(usersAtV4));
break;
case 5:
// Just add a new column that was added in version 5;
await m.addColumn(users, users.nextUser);
// And create the view on users
await m.createView(groupCount);
break;
case 6:
await m.addColumn(users, users.birthday);
break;
case 7:
await m.createTable(notes);
break;
case 8:
// Added a unique key to the users table
await m.alterTable(TableMigration(v8.Users(this)));
break;
case 9:
// Added a check to the users table
await m.alterTable(TableMigration(users));
break;
}
}
},
onUpgrade: _upgrade,
beforeOpen: (details) async {
// For Flutter apps, this should be wrapped in an if (kDebugMode) as
// suggested here: https://drift.simonbinder.eu/docs/advanced-features/migrations/#verifying-a-database-schema-at-runtime
@ -80,4 +26,50 @@ class Database extends _$Database {
},
);
}
static final _upgrade = stepByStep(
from1To2: (m, schema) async {
// Migration from 1 to 2: Add name column in users. Use "no name"
// as a default value.
await m.alterTable(
TableMigration(
schema.users,
columnTransformer: {
schema.users.name: const Constant<String>('no name'),
},
newColumns: [schema.users.name],
),
);
},
from2To3: (m, schema) async => m.createTable(schema.groups),
from3To4: (m, schema) async {
// Migration from 3 to 4: users.name now has a default value
// No need to transform any data, just re-create the table
final usersAtV4 = schema.users;
await m.alterTable(TableMigration(usersAtV4));
},
from4To5: (m, schema) async {
// Just add a new column that was added in version 5;
await m.addColumn(schema.users, schema.users.nextUser);
// And create the view on users
await m.createView(schema.groupCount);
},
from5To6: (m, schema) async {
await m.addColumn(schema.users, schema.users.birthday);
},
from6To7: (m, schema) async {
await m.createTable(schema.notes);
},
from7To8: (m, schema) async {
// Added a unique key to the users table
await m.alterTable(TableMigration(schema.users));
},
from8To9: (m, schema) async {
// Added a check to the users table
await m.alterTable(TableMigration(schema.users));
},
);
}

View File

@ -874,7 +874,7 @@ class GroupCount extends ViewInfo<GroupCount, GroupCountData>
@override
Query? get query => null;
@override
Set<String> get readTables => const {'groups', 'users'};
Set<String> get readTables => const {'users', 'groups'};
}
abstract class _$Database extends GeneratedDatabase {

View File

@ -1,43 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
import 'package:drift/internal/migrations.dart';
import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
import 'schema_v3.dart' as v3;
import 'schema_v4.dart' as v4;
import 'schema_v5.dart' as v5;
import 'schema_v6.dart' as v6;
import 'schema_v7.dart' as v7;
import 'schema_v8.dart' as v8;
import 'schema_v9.dart' as v9;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
switch (version) {
case 1:
return v1.DatabaseAtV1(db);
case 2:
return v2.DatabaseAtV2(db);
case 3:
return v3.DatabaseAtV3(db);
case 4:
return v4.DatabaseAtV4(db);
case 5:
return v5.DatabaseAtV5(db);
case 6:
return v6.DatabaseAtV6(db);
case 7:
return v7.DatabaseAtV7(db);
case 8:
return v8.DatabaseAtV8(db);
case 9:
return v9.DatabaseAtV9(db);
default:
throw MissingSchemaException(
version, const {1, 2, 3, 4, 5, 6, 7, 8, 9});
}
}
}

View File

@ -1,47 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
@override
List<GeneratedColumn> get $columns => [id];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class DatabaseAtV1 extends GeneratedDatabase {
DatabaseAtV1(QueryExecutor e) : super(e);
late final Users users = Users(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [users];
@override
int get schemaVersion => 1;
}

View File

@ -1,50 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
@override
List<GeneratedColumn> get $columns => [id, name];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class DatabaseAtV2 extends GeneratedDatabase {
DatabaseAtV2(QueryExecutor e) : super(e);
late final Users users = Users(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [users];
@override
int get schemaVersion => 2;
}

View File

@ -1,101 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
@override
List<GeneratedColumn> get $columns => [id, name];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class DatabaseAtV3 extends GeneratedDatabase {
DatabaseAtV3(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [users, groups];
@override
int get schemaVersion => 3;
}

View File

@ -1,103 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
@override
List<GeneratedColumn> get $columns => [id, name];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class DatabaseAtV4 extends GeneratedDatabase {
DatabaseAtV4(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [users, groups];
@override
int get schemaVersion => 4;
}

View File

@ -1,154 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
@override
List<GeneratedColumn> get $columns => [id, name, nextUser];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class GroupCount extends ViewInfo<GroupCount, Never> implements HasResultSet {
final String? _alias;
@override
final DatabaseAtV5 attachedDatabase;
GroupCount(this.attachedDatabase, [this._alias]);
@override
List<GeneratedColumn> get $columns => [id, name, nextUser, groupCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'group_count';
@override
String get createViewStmt =>
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users';
@override
GroupCount get asDslTable => this;
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
late final GeneratedColumn<int> id =
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int);
late final GeneratedColumn<int> groupCount = GeneratedColumn<int>(
'group_count', aliasedName, false,
type: DriftSqlType.int);
@override
GroupCount createAlias(String alias) {
return GroupCount(attachedDatabase, alias);
}
@override
Query? get query => null;
@override
Set<String> get readTables => const {};
}
class DatabaseAtV5 extends GeneratedDatabase {
DatabaseAtV5(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
late final GroupCount groupCount = GroupCount(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[users, groups, groupCount];
@override
int get schemaVersion => 5;
}

View File

@ -1,164 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
@override
List<GeneratedColumn> get $columns => [id, name, birthday, nextUser];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class GroupCount extends ViewInfo<GroupCount, Never> implements HasResultSet {
final String? _alias;
@override
final DatabaseAtV6 attachedDatabase;
GroupCount(this.attachedDatabase, [this._alias]);
@override
List<GeneratedColumn> get $columns =>
[id, name, birthday, nextUser, groupCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'group_count';
@override
String get createViewStmt =>
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users';
@override
GroupCount get asDslTable => this;
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
late final GeneratedColumn<int> id =
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string);
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int);
late final GeneratedColumn<int> groupCount = GeneratedColumn<int>(
'group_count', aliasedName, false,
type: DriftSqlType.int);
@override
GroupCount createAlias(String alias) {
return GroupCount(attachedDatabase, alias);
}
@override
Query? get query => null;
@override
Set<String> get readTables => const {};
}
class DatabaseAtV6 extends GeneratedDatabase {
DatabaseAtV6(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
late final GroupCount groupCount = GroupCount(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[users, groups, groupCount];
@override
int get schemaVersion => 6;
@override
DriftDatabaseOptions get options =>
const DriftDatabaseOptions(storeDateTimeAsText: true);
}

View File

@ -1,208 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
@override
List<GeneratedColumn> get $columns => [id, name, birthday, nextUser];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class GroupCount extends ViewInfo<GroupCount, Never> implements HasResultSet {
final String? _alias;
@override
final DatabaseAtV7 attachedDatabase;
GroupCount(this.attachedDatabase, [this._alias]);
@override
List<GeneratedColumn> get $columns =>
[id, name, birthday, nextUser, groupCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'group_count';
@override
String get createViewStmt =>
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users';
@override
GroupCount get asDslTable => this;
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
late final GeneratedColumn<int> id =
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string);
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int);
late final GeneratedColumn<int> groupCount = GeneratedColumn<int>(
'group_count', aliasedName, false,
type: DriftSqlType.int);
@override
GroupCount createAlias(String alias) {
return GroupCount(attachedDatabase, alias);
}
@override
Query? get query => null;
@override
Set<String> get readTables => const {};
}
class Notes extends Table with TableInfo, VirtualTableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Notes(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'content', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> searchTerms = GeneratedColumn<String>(
'search_terms', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [title, content, searchTerms];
@override
String get aliasedName => _alias ?? 'notes';
@override
String get actualTableName => 'notes';
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Notes createAlias(String alias) {
return Notes(attachedDatabase, alias);
}
@override
String get moduleAndArgs =>
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")';
}
class DatabaseAtV7 extends GeneratedDatabase {
DatabaseAtV7(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
late final GroupCount groupCount = GroupCount(this);
late final Notes notes = Notes(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[users, groups, groupCount, notes];
@override
int get schemaVersion => 7;
@override
DriftDatabaseOptions get options =>
const DriftDatabaseOptions(storeDateTimeAsText: true);
}

View File

@ -1,212 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES "users" ("id")'));
@override
List<GeneratedColumn> get $columns => [id, name, birthday, nextUser];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [
{name, birthday},
];
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users (id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY (id)'];
@override
bool get dontWriteConstraints => true;
}
class GroupCount extends ViewInfo<GroupCount, Never> implements HasResultSet {
final String? _alias;
@override
final DatabaseAtV8 attachedDatabase;
GroupCount(this.attachedDatabase, [this._alias]);
@override
List<GeneratedColumn> get $columns =>
[id, name, birthday, nextUser, groupCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'group_count';
@override
String get createViewStmt =>
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users';
@override
GroupCount get asDslTable => this;
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
late final GeneratedColumn<int> id =
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string);
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int);
late final GeneratedColumn<int> groupCount = GeneratedColumn<int>(
'group_count', aliasedName, false,
type: DriftSqlType.int);
@override
GroupCount createAlias(String alias) {
return GroupCount(attachedDatabase, alias);
}
@override
Query? get query => null;
@override
Set<String> get readTables => const {};
}
class Notes extends Table with TableInfo, VirtualTableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Notes(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'content', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> searchTerms = GeneratedColumn<String>(
'search_terms', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [title, content, searchTerms];
@override
String get aliasedName => _alias ?? 'notes';
@override
String get actualTableName => 'notes';
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Notes createAlias(String alias) {
return Notes(attachedDatabase, alias);
}
@override
String get moduleAndArgs =>
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")';
}
class DatabaseAtV8 extends GeneratedDatabase {
DatabaseAtV8(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
late final GroupCount groupCount = GroupCount(this);
late final Notes notes = Notes(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[users, groups, groupCount, notes];
@override
int get schemaVersion => 8;
@override
DriftDatabaseOptions get options =>
const DriftDatabaseOptions(storeDateTimeAsText: true);
}

View File

@ -1,215 +0,0 @@
// GENERATED CODE, DO NOT EDIT BY HAND.
// ignore_for_file: type=lint
//@dart=2.12
import 'package:drift/drift.dart';
class Users extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Users(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
hasAutoIncrement: true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant('name'));
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultConstraints:
GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
@override
List<GeneratedColumn> get $columns => [id, name, birthday, nextUser];
@override
String get aliasedName => _alias ?? 'users';
@override
String get actualTableName => 'users';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
List<Set<GeneratedColumn>> get uniqueKeys => [
{name, birthday},
];
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Users createAlias(String alias) {
return Users(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['CHECK (LENGTH(name) < 10)'];
}
class Groups extends Table with TableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Groups(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL');
late final GeneratedColumn<bool> deleted = GeneratedColumn<bool>(
'deleted', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: false,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression('FALSE'));
late final GeneratedColumn<int> owner = GeneratedColumn<int>(
'owner', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL REFERENCES users(id)');
@override
List<GeneratedColumn> get $columns => [id, title, deleted, owner];
@override
String get aliasedName => _alias ?? 'groups';
@override
String get actualTableName => 'groups';
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Groups createAlias(String alias) {
return Groups(attachedDatabase, alias);
}
@override
List<String> get customConstraints => const ['PRIMARY KEY(id)'];
@override
bool get dontWriteConstraints => true;
}
class Notes extends Table with TableInfo, VirtualTableInfo {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
Notes(this.attachedDatabase, [this._alias]);
late final GeneratedColumn<String> title = GeneratedColumn<String>(
'title', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> content = GeneratedColumn<String>(
'content', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
late final GeneratedColumn<String> searchTerms = GeneratedColumn<String>(
'search_terms', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: true,
$customConstraints: '');
@override
List<GeneratedColumn> get $columns => [title, content, searchTerms];
@override
String get aliasedName => _alias ?? 'notes';
@override
String get actualTableName => 'notes';
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
@override
Notes createAlias(String alias) {
return Notes(attachedDatabase, alias);
}
@override
String get moduleAndArgs =>
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")';
}
class GroupCount extends ViewInfo<GroupCount, Never> implements HasResultSet {
final String? _alias;
@override
final DatabaseAtV9 attachedDatabase;
GroupCount(this.attachedDatabase, [this._alias]);
@override
List<GeneratedColumn> get $columns =>
[id, name, birthday, nextUser, groupCount];
@override
String get aliasedName => _alias ?? entityName;
@override
String get entityName => 'group_count';
@override
String get createViewStmt =>
'CREATE VIEW group_count AS SELECT\n users.*,\n (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count\n FROM users;';
@override
GroupCount get asDslTable => this;
@override
Never map(Map<String, dynamic> data, {String? tablePrefix}) {
throw UnsupportedError('TableInfo.map in schema verification code');
}
late final GeneratedColumn<int> id =
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false,
type: DriftSqlType.string);
late final GeneratedColumn<DateTime> birthday = GeneratedColumn<DateTime>(
'birthday', aliasedName, true,
type: DriftSqlType.dateTime);
late final GeneratedColumn<int> nextUser = GeneratedColumn<int>(
'next_user', aliasedName, true,
type: DriftSqlType.int);
late final GeneratedColumn<int> groupCount = GeneratedColumn<int>(
'group_count', aliasedName, false,
type: DriftSqlType.int);
@override
GroupCount createAlias(String alias) {
return GroupCount(attachedDatabase, alias);
}
@override
Query? get query => null;
@override
Set<String> get readTables => const {};
}
class DatabaseAtV9 extends GeneratedDatabase {
DatabaseAtV9(QueryExecutor e) : super(e);
late final Users users = Users(this);
late final Groups groups = Groups(this);
late final Notes notes = Notes(this);
late final GroupCount groupCount = GroupCount(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities =>
[users, groups, notes, groupCount];
@override
int get schemaVersion => 9;
@override
DriftDatabaseOptions get options =>
const DriftDatabaseOptions(storeDateTimeAsText: true);
}

View File

@ -0,0 +1,644 @@
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart'; // ignore_for_file: type=lint
// GENERATED BY drift_dev, DO NOT MODIFY.
final class _S2 extends i0.VersionedSchema {
_S2({required super.database}) : super(version: 2);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
];
late final Shape0 users = Shape0(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
],
attachedDatabase: database,
),
alias: null);
}
class Shape0 extends i0.VersionedTable {
Shape0({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<int> _column_0(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
hasAutoIncrement: true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
i1.GeneratedColumn<String>('name', aliasedName, false,
type: i1.DriftSqlType.string);
final class _S3 extends i0.VersionedSchema {
_S3({required super.database}) : super(version: 3);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
];
late final Shape0 users = Shape0(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
class Shape1 extends i0.VersionedTable {
Shape1({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get deleted =>
columnsByName['deleted']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<int> get owner =>
columnsByName['owner']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_2(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
type: i1.DriftSqlType.int, $customConstraints: 'NOT NULL');
i1.GeneratedColumn<String> _column_3(String aliasedName) =>
i1.GeneratedColumn<String>('title', aliasedName, false,
type: i1.DriftSqlType.string, $customConstraints: 'NOT NULL');
i1.GeneratedColumn<bool> _column_4(String aliasedName) =>
i1.GeneratedColumn<bool>('deleted', aliasedName, true,
type: i1.DriftSqlType.bool,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression<bool>('FALSE'));
i1.GeneratedColumn<int> _column_5(String aliasedName) =>
i1.GeneratedColumn<int>('owner', aliasedName, false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL REFERENCES users (id)');
final class _S4 extends i0.VersionedSchema {
_S4({required super.database}) : super(version: 4);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
];
late final Shape0 users = Shape0(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_6,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
}
i1.GeneratedColumn<String> _column_6(String aliasedName) =>
i1.GeneratedColumn<String>('name', aliasedName, false,
type: i1.DriftSqlType.string, defaultValue: const Constant('name'));
final class _S5 extends i0.VersionedSchema {
_S5({required super.database}) : super(version: 5);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
groupCount,
];
late final Shape2 users = Shape2(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_6,
_column_7,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 groupCount = Shape3(
source: i0.VersionedView(
entityName: 'group_count',
createViewStmt:
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users',
columns: [
_column_8,
_column_1,
_column_9,
_column_10,
],
attachedDatabase: database,
),
alias: null);
}
class Shape2 extends i0.VersionedTable {
Shape2({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get nextUser =>
columnsByName['next_user']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_7(String aliasedName) =>
i1.GeneratedColumn<int>('next_user', aliasedName, true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
class Shape3 extends i0.VersionedView {
Shape3({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<int> get nextUser =>
columnsByName['next_user']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get groupCount =>
columnsByName['group_count']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<int> _column_8(String aliasedName) =>
i1.GeneratedColumn<int>('id', aliasedName, false,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_9(String aliasedName) =>
i1.GeneratedColumn<int>('next_user', aliasedName, true,
type: i1.DriftSqlType.int);
i1.GeneratedColumn<int> _column_10(String aliasedName) =>
i1.GeneratedColumn<int>('group_count', aliasedName, false,
type: i1.DriftSqlType.int);
final class _S6 extends i0.VersionedSchema {
_S6({required super.database}) : super(version: 6);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
groupCount,
];
late final Shape4 users = Shape4(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_6,
_column_11,
_column_7,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 groupCount = Shape5(
source: i0.VersionedView(
entityName: 'group_count',
createViewStmt:
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users',
columns: [
_column_8,
_column_1,
_column_11,
_column_9,
_column_10,
],
attachedDatabase: database,
),
alias: null);
}
class Shape4 extends i0.VersionedTable {
Shape4({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get birthday =>
columnsByName['birthday']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get nextUser =>
columnsByName['next_user']! as i1.GeneratedColumn<int>;
}
i1.GeneratedColumn<DateTime> _column_11(String aliasedName) =>
i1.GeneratedColumn<DateTime>('birthday', aliasedName, true,
type: i1.DriftSqlType.dateTime);
class Shape5 extends i0.VersionedView {
Shape5({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get name =>
columnsByName['name']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<DateTime> get birthday =>
columnsByName['birthday']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<int> get nextUser =>
columnsByName['next_user']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get groupCount =>
columnsByName['group_count']! as i1.GeneratedColumn<int>;
}
final class _S7 extends i0.VersionedSchema {
_S7({required super.database}) : super(version: 7);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
groupCount,
notes,
];
late final Shape4 users = Shape4(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_6,
_column_11,
_column_7,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 groupCount = Shape5(
source: i0.VersionedView(
entityName: 'group_count',
createViewStmt:
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users',
columns: [
_column_8,
_column_1,
_column_11,
_column_9,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 notes = Shape6(
source: i0.VersionedVirtualTable(
entityName: 'notes',
moduleAndArgs:
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")',
columns: [
_column_12,
_column_13,
_column_14,
],
attachedDatabase: database,
),
alias: null);
}
class Shape6 extends i0.VersionedVirtualTable {
Shape6({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<String> get title =>
columnsByName['title']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get content =>
columnsByName['content']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get searchTerms =>
columnsByName['search_terms']! as i1.GeneratedColumn<String>;
}
i1.GeneratedColumn<String> _column_12(String aliasedName) =>
i1.GeneratedColumn<String>('title', aliasedName, false,
type: i1.DriftSqlType.string, $customConstraints: '');
i1.GeneratedColumn<String> _column_13(String aliasedName) =>
i1.GeneratedColumn<String>('content', aliasedName, false,
type: i1.DriftSqlType.string, $customConstraints: '');
i1.GeneratedColumn<String> _column_14(String aliasedName) =>
i1.GeneratedColumn<String>('search_terms', aliasedName, false,
type: i1.DriftSqlType.string, $customConstraints: '');
final class _S8 extends i0.VersionedSchema {
_S8({required super.database}) : super(version: 8);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
groupCount,
notes,
];
late final Shape4 users = Shape4(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'UNIQUE(name, birthday)',
],
columns: [
_column_0,
_column_6,
_column_11,
_column_15,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY (id)',
],
columns: [
_column_2,
_column_3,
_column_4,
_column_5,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 groupCount = Shape5(
source: i0.VersionedView(
entityName: 'group_count',
createViewStmt:
'CREATE VIEW group_count AS SELECT users.*, (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count FROM users',
columns: [
_column_8,
_column_1,
_column_11,
_column_9,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 notes = Shape6(
source: i0.VersionedVirtualTable(
entityName: 'notes',
moduleAndArgs:
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")',
columns: [
_column_12,
_column_13,
_column_14,
],
attachedDatabase: database,
),
alias: null);
}
i1.GeneratedColumn<int> _column_15(String aliasedName) =>
i1.GeneratedColumn<int>('next_user', aliasedName, true,
type: i1.DriftSqlType.int,
defaultConstraints:
i1.GeneratedColumn.constraintIsAlways('REFERENCES "users" ("id")'));
final class _S9 extends i0.VersionedSchema {
_S9({required super.database}) : super(version: 9);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
users,
groups,
notes,
groupCount,
];
late final Shape4 users = Shape4(
source: i0.VersionedTable(
entityName: 'users',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'UNIQUE(name, birthday)',
'CHECK (LENGTH(name) < 10)',
],
columns: [
_column_0,
_column_6,
_column_11,
_column_7,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 groups = Shape1(
source: i0.VersionedTable(
entityName: 'groups',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(id)',
],
columns: [
_column_2,
_column_3,
_column_16,
_column_17,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 notes = Shape6(
source: i0.VersionedVirtualTable(
entityName: 'notes',
moduleAndArgs:
'fts5(title, content, search_terms, tokenize = "unicode61 tokenchars \'.\'")',
columns: [
_column_12,
_column_13,
_column_14,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 groupCount = Shape5(
source: i0.VersionedView(
entityName: 'group_count',
createViewStmt:
'CREATE VIEW group_count AS SELECT\n users.*,\n (SELECT COUNT(*) FROM "groups" WHERE owner = users.id) AS group_count\n FROM users;',
columns: [
_column_8,
_column_1,
_column_11,
_column_9,
_column_10,
],
attachedDatabase: database,
),
alias: null);
}
i1.GeneratedColumn<bool> _column_16(String aliasedName) =>
i1.GeneratedColumn<bool>('deleted', aliasedName, true,
type: i1.DriftSqlType.bool,
$customConstraints: 'DEFAULT FALSE',
defaultValue: const CustomExpression('FALSE'));
i1.GeneratedColumn<int> _column_17(String aliasedName) =>
i1.GeneratedColumn<int>('owner', aliasedName, false,
type: i1.DriftSqlType.int,
$customConstraints: 'NOT NULL REFERENCES users(id)');
i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, _S2 schema) from1To2,
required Future<void> Function(i1.Migrator m, _S3 schema) from2To3,
required Future<void> Function(i1.Migrator m, _S4 schema) from3To4,
required Future<void> Function(i1.Migrator m, _S5 schema) from4To5,
required Future<void> Function(i1.Migrator m, _S6 schema) from5To6,
required Future<void> Function(i1.Migrator m, _S7 schema) from6To7,
required Future<void> Function(i1.Migrator m, _S8 schema) from7To8,
required Future<void> Function(i1.Migrator m, _S9 schema) from8To9,
}) {
return i1.Migrator.stepByStepHelper(step: (currentVersion, database) async {
switch (currentVersion) {
case 1:
final schema = _S2(database: database);
final migrator = i1.Migrator(database, schema);
await from1To2(migrator, schema);
return 2;
case 2:
final schema = _S3(database: database);
final migrator = i1.Migrator(database, schema);
await from2To3(migrator, schema);
return 3;
case 3:
final schema = _S4(database: database);
final migrator = i1.Migrator(database, schema);
await from3To4(migrator, schema);
return 4;
case 4:
final schema = _S5(database: database);
final migrator = i1.Migrator(database, schema);
await from4To5(migrator, schema);
return 5;
case 5:
final schema = _S6(database: database);
final migrator = i1.Migrator(database, schema);
await from5To6(migrator, schema);
return 6;
case 6:
final schema = _S7(database: database);
final migrator = i1.Migrator(database, schema);
await from6To7(migrator, schema);
return 7;
case 7:
final schema = _S8(database: database);
final migrator = i1.Migrator(database, schema);
await from7To8(migrator, schema);
return 8;
case 8:
final schema = _S9(database: database);
final migrator = i1.Migrator(database, schema);
await from8To9(migrator, schema);
return 9;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
});
}

View File

@ -3,7 +3,7 @@ publish_to: none
version: 1.0.0
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=3.0.0 <4.0.0'
dependencies:
drift:

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: args
sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
async:
dependency: transitive
description:
@ -53,10 +53,10 @@ packages:
dependency: transitive
description:
name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
url: "https://pub.dev"
source: hosted
version: "0.3.5"
version: "0.4.0"
collection:
dependency: transitive
description:
@ -85,18 +85,18 @@ packages:
dependency: transitive
description:
name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: "772db3d53d23361d4ffcf5a9bb091cf3ee9b22f2be52cd107cd7a2683a89ba0e"
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.1"
http:
dependency: transitive
description:
@ -133,18 +133,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
melos:
dependency: "direct dev"
description:
name: melos
sha256: "993ac467e7a36bd832a6cdabbe18a0487c30bc52b5cca14e476a824679ebdce0"
sha256: ccbb6ecd8bb3f08ae8f9ce22920d816bff325a98940c845eda0257cd395503ac
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.1.0"
meta:
dependency: transitive
description:
@ -253,10 +253,10 @@ packages:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -277,10 +277,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: daadc9baabec998b062c9091525aa95786508b1c48e9c30f1f891b8bf6ff2e64
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
version: "0.6.0"
typed_data:
dependency: transitive
description: