Skip to content

Commit

Permalink
Merge branch 'develop' into new-schema-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Nov 2, 2024
2 parents 7f87122 + 3d446ab commit f85c1c8
Show file tree
Hide file tree
Showing 67 changed files with 1,407 additions and 683 deletions.
20 changes: 7 additions & 13 deletions docs/docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ drift_dev () {
run_webdev(){
echo "Running webdev..."
# The below command will compile the dart code in `/web` to js & run build_runner
dart run webdev build -o web:build/web -- --delete-conflicting-outputs --release
if [ $1 -eq 1 ]; then
echo "Running dev build"
dart run webdev build -o web:build/web -- --delete-conflicting-outputs
else
dart run webdev build -o web:build/web -- --delete-conflicting-outputs --release
fi
if [ $? -ne 0 ]; then
echo "Failed to build the project"
exit 1
Expand Down Expand Up @@ -131,15 +136,7 @@ elif [ $arg1 == "serve" ]; then
echo "Serving the project..."

drift_dev

dart run build_runner build --delete-conflicting-outputs
if [ $? -ne 0 ]; then
echo "Failed to build the project"
exit 1
fi

run_webdev

run_webdev 1
build_container

serve_mkdocs &
Expand All @@ -155,6 +152,3 @@ else
echo "Invalid argument. Please use 'build' or 'serve'"
exit 1
fi



25 changes: 21 additions & 4 deletions docs/docs/Migrations/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,11 @@ If it sees anything unexpected, it will throw a `SchemaMismatch` exception to fa

!!! note "Writing testable migrations"


To test migrations _towards_ an old schema version (e.g. from `v1` to `v2` if your current version is `v3`),
your `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.
Or, use [step-by-step migrations](step_by_step.md) which do this automatically.


## Verifying data integrity

Expand Down Expand Up @@ -96,9 +95,21 @@ This can then be used to manually create and verify data at a specific version:
## Verifying a database schema at runtime

Instead (or in addition to) [writing tests](#verifying-a-database-schema-at-runtime) to ensure your migrations work as they should,
you can use a new API from `drift_dev` 1.5.0 to verify the current schema without any additional setup.
`drift_dev` provides an API to verify the current schema at runtime without any additional setup on native platforms.


=== "Native"

{{ load_snippet('native','lib/snippets/migrations/runtime_verification.dart.excerpt.json', indent=4) }}


=== "Web (since drift 2.22)"

Starting from drift version 2.22, this functionality is also available on the web. Since the method internally
opens another database to create the expected schema, the web variant needs to be configured explicitly:

{{ load_snippet('web','lib/snippets/migrations/runtime_verification_web.dart.excerpt.json', indent=4) }}

{{ load_snippet('(full)','lib/snippets/migrations/runtime_verification.dart.excerpt.json') }}

When you use `validateDatabaseSchema`, drift will transparently:

Expand All @@ -110,3 +121,9 @@ When you use `validateDatabaseSchema`, drift will transparently:
When a mismatch is found, an exception with a message explaining exactly where another value was expected will
be thrown.
This allows you to find issues with your schema migrations quickly.

!!! tip "Also available in DevTools"

Ensuring that the current schema matches the expected state is also a feature available in Drift's
[DevTools extension]({ '../Tools/devtools.md' }).
The extensions also allow resetting a database, which might be useful when working on or debugging migrations.
3 changes: 3 additions & 0 deletions docs/docs/generation_options/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ At the moment, drift supports these options:
data classes.
- `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final
by default. You can make them mutable by setting `mutable_classes: true`.
- `row_class_constructor_all_required` (defaults to `false`): All parameters for generated row classes
(both for tables and custom queries) are `required`, regardless of whether they are nullable.
Since these classes always represent a full row, the parameters can be made `required` to reflect that.
- `raw_result_set_data`: The generator will expose the underlying `QueryRow` for generated result set classes
- `apply_converters_on_variables` (defaults to `true`): Applies type converters to variables in compiled statements.
- `generate_values_in_copy_with` (defaults to `true`): Generates a `Value<T?>` instead of `T?` for nullable columns in `copyWith`. This allows to set
Expand Down
8 changes: 4 additions & 4 deletions docs/lib/snippets/dart_api/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,22 +230,22 @@ extension ManagerExamples on AppDatabase {
// #docregion manager_prefetch_references
Future<void> referencesPrefetch() async {
/// Get each todo, along with a its categories
final categoriesWithReferences = await managers.todoItems
final todosWithRefs = await managers.todoItems
.withReferences(
(prefetch) => prefetch(category: true),
)
.get();
for (final (todo, refs) in categoriesWithReferences) {
for (final (todo, refs) in todosWithRefs) {
final category = refs.category?.prefetchedData?.firstOrNull;
// No longer needed
// final category = await refs.category?.getSingle();
}

/// This also works in the reverse
final todosWithRefs = await managers.todoCategory
final categoriesWithRefs = await managers.todoCategory
.withReferences((prefetch) => prefetch(todoItemsRefs: true))
.get();
for (final (category, refs) in todosWithRefs) {
for (final (category, refs) in categoriesWithRefs) {
final todos = refs.todoItemsRefs.prefetchedData;
// No longer needed
//final todos = await refs.todoItemsRefs.get();
Expand Down
14 changes: 7 additions & 7 deletions docs/lib/snippets/migrations/runtime_verification.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import 'package:drift/drift.dart';

// #docregion
// #docregion native
// import the migrations tooling
import 'package:drift_dev/api/migrations.dart';
// #enddocregion
import 'package:drift_dev/api/migrations_native.dart';
// #enddocregion native

const kDebugMode = true;

abstract class _$MyDatabase extends GeneratedDatabase {
_$MyDatabase(super.executor);
}

// #docregion
// #docregion native

class MyDatabase extends _$MyDatabase {
// #enddocregion
// #enddocregion native
MyDatabase(super.executor);

@override
Expand All @@ -24,7 +24,7 @@ class MyDatabase extends _$MyDatabase {
@override
int get schemaVersion => throw UnimplementedError();

// #docregion
// #docregion native
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (m) async {/* ... */},
Expand All @@ -40,4 +40,4 @@ class MyDatabase extends _$MyDatabase {
},
);
}
// #enddocregion
// #enddocregion native
49 changes: 49 additions & 0 deletions docs/lib/snippets/migrations/runtime_verification_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:drift/drift.dart';

// #docregion web
// import the migrations tooling
import 'package:drift_dev/api/migrations_web.dart';
import 'package:sqlite3/wasm.dart';
// #enddocregion web

const kDebugMode = true;

abstract class _$MyDatabase extends GeneratedDatabase {
_$MyDatabase(super.executor);
}

// #docregion web

class MyDatabase extends _$MyDatabase {
// #enddocregion web
MyDatabase(super.executor);

@override
Iterable<TableInfo<Table, dynamic>> get allTables =>
throw UnimplementedError();

@override
int get schemaVersion => throw UnimplementedError();

// #docregion web
@override
MigrationStrategy get migration => MigrationStrategy(
onCreate: (m) async {/* ... */},
onUpgrade: (m, from, to) async {/* your existing migration logic */},
beforeOpen: (details) async {
// your existing beforeOpen callback, enable foreign keys, etc.

// This check pulls in a fair amount of code that's not needed
// anywhere else, so we recommend only doing it in debug builds.
if (kDebugMode) {
// The web schema verifier needs a sqlite3 instance to open another
// version of your database so that the two can be compared.
final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('/'));
sqlite3.registerVirtualFileSystem(InMemoryFileSystem(),
makeDefault: true);
await validateDatabaseSchema(sqlite3: sqlite3);
}
},
);
}
// #enddocregion web
2 changes: 1 addition & 1 deletion docs/lib/snippets/migrations/tests/schema_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// #docregion setup
import 'package:test/test.dart';
import 'package:drift_dev/api/migrations.dart';
import 'package:drift_dev/api/migrations_native.dart';

// The generated directory from before.
import 'generated_migrations/schema.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:test/test.dart';
import 'package:drift_dev/api/migrations.dart';
import 'package:drift_dev/api/migrations_native.dart';

import '../migrations.dart';
import 'generated_migrations/schema.dart';
Expand Down
4 changes: 2 additions & 2 deletions docs/mkdocs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ nav:
- Migrations/api.md
- type_converters.md
- Code Generation:
- Getting Started: generation_options/index.md
- Options overview: generation_options/index.md
- generation_options/modular.md
- generation_options/in_other_builders.md
- Platforms:
- Getting Started: Platforms/index.md
- Platforms overview: Platforms/index.md
- Platforms/vm.md
- Platforms/web.md
- Platforms/postgres.md
Expand Down
1 change: 1 addition & 0 deletions drift/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Add `sqliteAny()` method to tables to declare `ANY` columns.
- Add missing parentheses around adjacent expressions of the same precedence.
- WASM: Report worker failures to make them easier to diagnose.

## 2.21.0

Expand Down
3 changes: 1 addition & 2 deletions drift/example/main.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 69 additions & 6 deletions drift/lib/internal/export_schema.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:isolate';

import 'package:drift/drift.dart';
Expand All @@ -20,15 +21,36 @@ final class SchemaExporter {
/// all statements that were executed in the process.
Future<List<String>> collectOnCreateStatements(
[SqlDialect dialect = SqlDialect.sqlite]) async {
final collector = CollectCreateStatements(dialect);
final collected = await _collect(dialects: [dialect]);
return collected.collectedStatements.map((e) => e.stmt).toList();
}

Future<_CollectByDialect> _collect({
required Iterable<SqlDialect> dialects,
List<String>? elementNames,
}) async {
final interceptor = _CollectByDialect();
final collector =
CollectCreateStatements(SqlDialect.sqlite).interceptWith(interceptor);
final db = _database(collector);

await db.runConnectionZoned(BeforeOpenRunner(db, collector), () async {
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
final migrator = db.createMigrator();
await migrator.createAll();

for (final entity in db.allSchemaEntities) {
if (elementNames == null || elementNames.contains(entity.entityName)) {
interceptor.currentName = entity.entityName;
for (final dialect in dialects) {
interceptor.currentDialect = dialect;

await migrator.create(entity);
}
}
}
});

return collector.statements;
return interceptor;
}

/// Creates a [SchemaExporter] with the [database], parses the single-argument
Expand All @@ -48,8 +70,49 @@ final class SchemaExporter {
GeneratedDatabase Function(QueryExecutor) database,
) async {
final export = SchemaExporter(database);
final statements = await export
.collectOnCreateStatements(SqlDialect.values.byName(args.single));
port.send(statements);

if (args case ['v2', final options]) {
final parsedOptions = json.decode(options);
final dialects = (parsedOptions['dialects'] as List)
.map((e) => SqlDialect.values.byName(e as String));
final elements = (parsedOptions['elements'] as List).cast<String>();

final result =
await export._collect(dialects: dialects, elementNames: elements);
final serialized = [
for (final row in result.collectedStatements)
[row.element, row.dialect.name, row.stmt]
];

port.send(serialized);
} else {
final statements = await export
.collectOnCreateStatements(SqlDialect.values.byName(args.single));
port.send(statements);
}
}
}

final class _CollectByDialect extends QueryInterceptor {
SqlDialect currentDialect = SqlDialect.sqlite;
String? currentName;

final List<({String element, SqlDialect dialect, String stmt})>
collectedStatements = [];

@override
SqlDialect dialect(QueryExecutor executor) {
return currentDialect;
}

@override
Future<void> runCustom(
QueryExecutor executor, String statement, List<Object?> args) {
if (currentName != null) {
collectedStatements.add(
(element: currentName!, dialect: currentDialect, stmt: statement));
}

return executor.runCustom(statement, args);
}
}
4 changes: 2 additions & 2 deletions drift/lib/internal/migrations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import 'package:drift/drift.dart';
/// The implementation of this class is generated through the `drift_dev`
/// CLI tool.
/// Typically, you don't use this class directly but rather through the
/// `SchemaVerifier` class part of `package:drift_dev/api/migrations.dart`
/// library.
/// `SchemaVerifier` class part of `package:drift_dev/api/migrations_native.dart`
/// (or it's web pendant) library.
abstract class SchemaInstantiationHelper {
/// Creates a database with the state of an old schema [version] and using the
/// given underlying [db] connection.
Expand Down
2 changes: 2 additions & 0 deletions drift/lib/src/web/wasm_setup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,14 @@ class WasmDatabaseOpener {
} on Object {
_sharedWorker?.close();
_sharedWorker = null;
missingFeatures.add(MissingBrowserFeature.workerError);
}
try {
await _probeDedicated();
} on Object {
_dedicatedWorker?.close();
_dedicatedWorker = null;
missingFeatures.add(MissingBrowserFeature.workerError);
}

return _ProbeResult(availableImplementations, existingDatabases.toList(),
Expand Down
8 changes: 8 additions & 0 deletions drift/lib/src/web/wasm_setup/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ enum MissingBrowserFeature {
/// To enable this feature in most browsers, you need to serve your app with
/// two [special headers](https://web.dev/coop-coep/).
sharedArrayBuffers,

/// An error occurred when trying to connect to a shared or dedicated drift
/// worker.
///
/// This either indicates a bug in drift (with the worker crashing) or an
/// issue in your setup (for instance, this could occur when the drift worker
/// file is missing from your `web/` folder).
workerError,
}

/// Information about an existing web database, consisting of its
Expand Down
Loading

0 comments on commit f85c1c8

Please sign in to comment.