mirror of https://github.com/AMT-Cheif/drift.git
Add docs for step-by-step migrations
This commit is contained in:
parent
ed9aeb866a
commit
312fa3219f
|
@ -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":[]}}]}
|
|
@ -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":[]}}]}
|
|
@ -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":[]}}]}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -1,18 +1,41 @@
|
|||
/// 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;
|
||||
|
||||
DatabaseSchemaEntity lookup(String name) {
|
||||
return entities.singleWhere((element) => element.entityName == name);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
@ -36,6 +59,10 @@ class VersionedTable extends Table with TableInfo<Table, QueryRow> {
|
|||
@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,
|
||||
|
@ -49,6 +76,7 @@ class VersionedTable extends Table with TableInfo<Table, QueryRow> {
|
|||
$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,
|
||||
|
@ -84,11 +112,13 @@ class VersionedTable extends Table with TableInfo<Table, QueryRow> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
@ -101,6 +131,8 @@ class VersionedVirtualTable extends VersionedTable
|
|||
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,
|
||||
|
@ -115,6 +147,8 @@ class VersionedVirtualTable extends VersionedTable
|
|||
}
|
||||
}
|
||||
|
||||
/// A constructed from individual fields instead of being generated with a
|
||||
/// dedicated class.
|
||||
class VersionedView implements ViewInfo<HasResultSet, QueryRow>, HasResultSet {
|
||||
@override
|
||||
final String entityName;
|
||||
|
@ -138,6 +172,7 @@ class VersionedView implements ViewInfo<HasResultSet, QueryRow>, HasResultSet {
|
|||
@override
|
||||
final DatabaseConnectionUser attachedDatabase;
|
||||
|
||||
/// Create a view from the individual fields on [ViewInfo].
|
||||
VersionedView({
|
||||
required this.entityName,
|
||||
required this.attachedDatabase,
|
||||
|
@ -148,6 +183,7 @@ class VersionedView implements ViewInfo<HasResultSet, QueryRow>, HasResultSet {
|
|||
$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,
|
||||
|
|
|
@ -482,6 +482,12 @@ class Migrator {
|
|||
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(
|
||||
|
@ -493,7 +499,10 @@ class Migrator {
|
|||
final database = m._db;
|
||||
|
||||
for (var target = from; target < to;) {
|
||||
target = await step(target, database);
|
||||
final newVersion = await step(target, database);
|
||||
assert(newVersion > target);
|
||||
|
||||
target = newVersion;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ enum _ResultSetKind {
|
|||
|
||||
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;
|
||||
|
||||
|
@ -50,6 +51,14 @@ final class _TableShape {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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');
|
||||
|
@ -61,8 +70,16 @@ class SchemaVersionWriter {
|
|||
final Map<String, String> _columnCodeToFactory = {};
|
||||
final Map<_TableShape, String> _shapes = {};
|
||||
|
||||
SchemaVersionWriter(this.versions, this.libraryScope);
|
||||
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);
|
||||
|
@ -86,6 +103,16 @@ class SchemaVersionWriter {
|
|||
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) => (
|
||||
|
@ -130,11 +157,11 @@ class SchemaVersionWriter {
|
|||
});
|
||||
}
|
||||
|
||||
void _writeWithResultSet(DriftElementWithResultSet entity, Scope classScope,
|
||||
TextEmitter intoListWriter) {
|
||||
String _writeWithResultSet(
|
||||
DriftElementWithResultSet entity, TextEmitter writer) {
|
||||
final getterName = entity.dbGetterName;
|
||||
final shape = _shapeClass(entity);
|
||||
final writer = classScope.leaf()
|
||||
writer
|
||||
..write('late final $shape $getterName = ')
|
||||
..write('$shape(source: ');
|
||||
|
||||
|
@ -229,43 +256,60 @@ class SchemaVersionWriter {
|
|||
|
||||
_writeColumnsArgument(entity.columns, writer);
|
||||
writer.write('attachedDatabase: database,');
|
||||
writer.write('), alias: null);');
|
||||
writer.write('), alias: null)');
|
||||
|
||||
intoListWriter.write(getterName);
|
||||
return getterName!;
|
||||
}
|
||||
|
||||
void _writeEntity({
|
||||
String _writeEntity({
|
||||
required DriftSchemaElement element,
|
||||
required Scope classScope,
|
||||
required TextEmitter writer,
|
||||
required TextEmitter definition,
|
||||
}) {
|
||||
String name;
|
||||
|
||||
if (element is DriftElementWithResultSet) {
|
||||
_writeWithResultSet(element, classScope, writer);
|
||||
name = _writeWithResultSet(element, definition);
|
||||
} else if (element is DriftIndex) {
|
||||
writer
|
||||
..writeDriftRef('Index(')
|
||||
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) {
|
||||
writer
|
||||
..writeDriftRef('Trigger(')
|
||||
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 {
|
||||
writer.writeln('null');
|
||||
throw ArgumentError('Unhandled element type $element');
|
||||
}
|
||||
|
||||
definition.write(';');
|
||||
return name;
|
||||
}
|
||||
|
||||
void write() {
|
||||
for (final version in versions) {
|
||||
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')
|
||||
|
@ -273,27 +317,30 @@ class SchemaVersionWriter {
|
|||
..writeln('$versionClass({required super.database}): '
|
||||
'super(version: $versionNo);');
|
||||
|
||||
// Override the allEntities getters by VersionedSchema
|
||||
final allEntitiesWriter = versionScope.leaf()
|
||||
..write('@override')
|
||||
..write(' late final ')
|
||||
..write('@override late final ')
|
||||
..writeUriRef(AnnotatedDartCode.dartCore, 'List')
|
||||
..write('<')
|
||||
..writeDriftRef('DatabaseSchemaEntity')
|
||||
..write('> entities = [');
|
||||
|
||||
for (final entity in version.schema) {
|
||||
_writeEntity(
|
||||
element: entity,
|
||||
classScope: versionScope,
|
||||
writer: allEntitiesWriter,
|
||||
);
|
||||
allEntitiesWriter.write(',');
|
||||
// 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({');
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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:
|
||||
// The todoEntries.dueDate column was added in version 2.
|
||||
await m.addColumn(todoEntries, todoEntries.dueDate);
|
||||
break;
|
||||
case 3:
|
||||
// New triggers were added in version 3:
|
||||
await m.create(todosDelete);
|
||||
await m.create(todosUpdate);
|
||||
onUpgrade: stepByStep(
|
||||
from1To2: (m, schema) async {
|
||||
// The todoEntries.dueDate column was added in version 2.
|
||||
await m.addColumn(schema.todoEntries, schema.todoEntries.dueDate);
|
||||
},
|
||||
from2To3: (m, schema) async {
|
||||
// New triggers were added in version 3:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}),
|
||||
// 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(schema.todoEntries));
|
||||
},
|
||||
),
|
||||
beforeOpen: (details) async {
|
||||
// Make sure that foreign keys are enabled
|
||||
await customStatement('PRAGMA foreign_keys = ON');
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:drift/internal/versioned_schema.dart' as i0;
|
||||
import 'package:drift/drift.dart' as i1;
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/drift.dart'; // ignore_for_file: type=lint
|
||||
|
||||
final class _S1 extends i0.VersionedSchema {
|
||||
_S1({required super.database}) : super(version: 1);
|
||||
// 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,
|
||||
|
@ -16,6 +17,7 @@ final class _S1 extends i0.VersionedSchema {
|
|||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
|
@ -26,6 +28,8 @@ 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) =>
|
||||
|
@ -34,36 +38,6 @@ i1.GeneratedColumn<int> _column_0(String aliasedName) =>
|
|||
type: i1.DriftSqlType.int,
|
||||
defaultConstraints:
|
||||
i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
|
||||
|
||||
final class _S2 extends i0.VersionedSchema {
|
||||
_S2({required super.database}) : super(version: 2);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
users,
|
||||
];
|
||||
late final Shape1 users = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
],
|
||||
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 name =>
|
||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<String> _column_1(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>('name', aliasedName, false,
|
||||
type: i1.DriftSqlType.string);
|
||||
|
@ -75,7 +49,7 @@ final class _S3 extends i0.VersionedSchema {
|
|||
users,
|
||||
groups,
|
||||
];
|
||||
late final Shape1 users = Shape1(
|
||||
late final Shape0 users = Shape0(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -88,7 +62,7 @@ final class _S3 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -107,8 +81,8 @@ final class _S3 extends i0.VersionedSchema {
|
|||
alias: null);
|
||||
}
|
||||
|
||||
class Shape2 extends i0.VersionedTable {
|
||||
Shape2({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -142,7 +116,7 @@ final class _S4 extends i0.VersionedSchema {
|
|||
users,
|
||||
groups,
|
||||
];
|
||||
late final Shape1 users = Shape1(
|
||||
late final Shape0 users = Shape0(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -155,7 +129,7 @@ final class _S4 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -186,7 +160,7 @@ final class _S5 extends i0.VersionedSchema {
|
|||
groups,
|
||||
groupCount,
|
||||
];
|
||||
late final Shape3 users = Shape3(
|
||||
late final Shape2 users = Shape2(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -200,7 +174,7 @@ final class _S5 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -217,7 +191,7 @@ final class _S5 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape4 groupCount = Shape4(
|
||||
late final Shape3 groupCount = Shape3(
|
||||
source: i0.VersionedView(
|
||||
entityName: 'group_count',
|
||||
createViewStmt:
|
||||
|
@ -233,8 +207,8 @@ final class _S5 extends i0.VersionedSchema {
|
|||
alias: null);
|
||||
}
|
||||
|
||||
class Shape3 extends i0.VersionedTable {
|
||||
Shape3({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -249,8 +223,8 @@ i1.GeneratedColumn<int> _column_7(String aliasedName) =>
|
|||
defaultConstraints:
|
||||
i1.GeneratedColumn.constraintIsAlways('REFERENCES users (id)'));
|
||||
|
||||
class Shape4 extends i0.VersionedView {
|
||||
Shape4({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -279,7 +253,7 @@ final class _S6 extends i0.VersionedSchema {
|
|||
groups,
|
||||
groupCount,
|
||||
];
|
||||
late final Shape5 users = Shape5(
|
||||
late final Shape4 users = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -294,7 +268,7 @@ final class _S6 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -311,7 +285,7 @@ final class _S6 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape6 groupCount = Shape6(
|
||||
late final Shape5 groupCount = Shape5(
|
||||
source: i0.VersionedView(
|
||||
entityName: 'group_count',
|
||||
createViewStmt:
|
||||
|
@ -328,8 +302,8 @@ final class _S6 extends i0.VersionedSchema {
|
|||
alias: null);
|
||||
}
|
||||
|
||||
class Shape5 extends i0.VersionedTable {
|
||||
Shape5({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -344,8 +318,8 @@ i1.GeneratedColumn<DateTime> _column_11(String aliasedName) =>
|
|||
i1.GeneratedColumn<DateTime>('birthday', aliasedName, true,
|
||||
type: i1.DriftSqlType.dateTime);
|
||||
|
||||
class Shape6 extends i0.VersionedView {
|
||||
Shape6({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -367,7 +341,7 @@ final class _S7 extends i0.VersionedSchema {
|
|||
groupCount,
|
||||
notes,
|
||||
];
|
||||
late final Shape5 users = Shape5(
|
||||
late final Shape4 users = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -382,7 +356,7 @@ final class _S7 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -399,7 +373,7 @@ final class _S7 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape6 groupCount = Shape6(
|
||||
late final Shape5 groupCount = Shape5(
|
||||
source: i0.VersionedView(
|
||||
entityName: 'group_count',
|
||||
createViewStmt:
|
||||
|
@ -414,7 +388,7 @@ final class _S7 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape7 notes = Shape7(
|
||||
late final Shape6 notes = Shape6(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'notes',
|
||||
moduleAndArgs:
|
||||
|
@ -429,8 +403,8 @@ final class _S7 extends i0.VersionedSchema {
|
|||
alias: null);
|
||||
}
|
||||
|
||||
class Shape7 extends i0.VersionedVirtualTable {
|
||||
Shape7({required super.source, required super.alias}) : super.aliased();
|
||||
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 =>
|
||||
|
@ -458,7 +432,7 @@ final class _S8 extends i0.VersionedSchema {
|
|||
groupCount,
|
||||
notes,
|
||||
];
|
||||
late final Shape5 users = Shape5(
|
||||
late final Shape4 users = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -475,7 +449,7 @@ final class _S8 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -492,7 +466,7 @@ final class _S8 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape6 groupCount = Shape6(
|
||||
late final Shape5 groupCount = Shape5(
|
||||
source: i0.VersionedView(
|
||||
entityName: 'group_count',
|
||||
createViewStmt:
|
||||
|
@ -507,7 +481,7 @@ final class _S8 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape7 notes = Shape7(
|
||||
late final Shape6 notes = Shape6(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'notes',
|
||||
moduleAndArgs:
|
||||
|
@ -537,7 +511,7 @@ final class _S9 extends i0.VersionedSchema {
|
|||
notes,
|
||||
groupCount,
|
||||
];
|
||||
late final Shape5 users = Shape5(
|
||||
late final Shape4 users = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'users',
|
||||
withoutRowId: false,
|
||||
|
@ -555,7 +529,7 @@ final class _S9 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 groups = Shape2(
|
||||
late final Shape1 groups = Shape1(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
|
@ -572,7 +546,7 @@ final class _S9 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape7 notes = Shape7(
|
||||
late final Shape6 notes = Shape6(
|
||||
source: i0.VersionedVirtualTable(
|
||||
entityName: 'notes',
|
||||
moduleAndArgs:
|
||||
|
@ -585,7 +559,7 @@ final class _S9 extends i0.VersionedSchema {
|
|||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape6 groupCount = Shape6(
|
||||
late final Shape5 groupCount = Shape5(
|
||||
source: i0.VersionedView(
|
||||
entityName: 'group_count',
|
||||
createViewStmt:
|
||||
|
|
Loading…
Reference in New Issue