Skip to content

Commit 7eb560a

Browse files
committed
drift3: Support schema isolates
1 parent d4aff5d commit 7eb560a

8 files changed

Lines changed: 183 additions & 24 deletions

File tree

drift/lib/src/drift3_preview/internal/export_schema.dart

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import 'dart:convert';
12
import 'dart:isolate';
23

4+
import 'package:collection/collection.dart';
35
import 'package:meta/meta.dart';
46

57
import '../drift.dart';
68

7-
/// Generates `CREATE` statements for the given database [schema] for all
8-
/// [dialects] and sends generated statements through the send [port].
9+
/// Generates `CREATE` statements for the given [database] for all [dialects]
10+
/// and sends generated statements through the send [port].
911
///
1012
/// Each statement will be encoded as a `[index, name, sql]` list, where `index`
1113
/// is the index in [dialects], `name` is the [DatabaseSchemaEntity.entityName]
@@ -21,14 +23,24 @@ import '../drift.dart';
2123
/// of that.
2224
@internal
2325
void sendCreateStatements(
26+
List<String> args,
2427
SendPort port,
25-
DatabaseSchema schema,
26-
List<DriftDialect> dialects,
28+
GeneratedDatabase Function(DriftConnection) database,
29+
List<DriftDialectFactory> dialects,
2730
) {
28-
final statements = <List<Object>>[];
31+
final statements = <DriftDialect, List<_CollectedStatement>>{};
2932

30-
for (final (index, dialect) in dialects.indexed) {
31-
for (final entity in schema) {
33+
for (final dialectFactory in dialects) {
34+
final opened = database(
35+
DriftConnection(
36+
dialect: dialectFactory,
37+
openConnection: () => Future.error(UnsupportedError('Stub connection')),
38+
),
39+
);
40+
final dialect = opened.dialect;
41+
final statementsForDialect = <_CollectedStatement>[];
42+
43+
for (final entity in opened.schema) {
3244
StatementInfo compiled;
3345

3446
switch (entity) {
@@ -40,7 +52,51 @@ void sendCreateStatements(
4052
compiled = dialect.compile(definition);
4153
}
4254

43-
statements.add([index, entity.entityName, compiled.sql]);
55+
statementsForDialect.add(
56+
_CollectedStatement(entity.entityName, compiled.sql),
57+
);
58+
}
59+
60+
statements[dialect] = statementsForDialect;
61+
}
62+
63+
List<_CollectedStatement> statementsForDialect(String dialectName) {
64+
final dialect = KnownSqlDialect.values.byName(dialectName);
65+
final entry = statements.entries.firstWhereOrNull(
66+
(e) => e.key.known == dialect,
67+
);
68+
69+
if (entry == null) {
70+
throw ArgumentError(
71+
'Dialect ${dialect.name} is not registered on this database',
72+
);
4473
}
74+
75+
return entry.value;
4576
}
77+
78+
if (args case ['v2', final options]) {
79+
final parsedOptions = json.decode(options);
80+
final dialectNames = (parsedOptions['dialects'] as List).cast<String>();
81+
final encodedStatements = <List>[];
82+
83+
for (final name in dialectNames) {
84+
encodedStatements.addAll(
85+
statementsForDialect(name).map((e) => [e.element, name, e.stmt]),
86+
);
87+
}
88+
89+
port.send(encodedStatements);
90+
} else {
91+
port.send([
92+
for (final stmt in statementsForDialect(args.single)) stmt.stmt,
93+
]);
94+
}
95+
}
96+
97+
final class _CollectedStatement {
98+
final String element;
99+
final String stmt;
100+
101+
_CollectedStatement(this.element, this.stmt);
46102
}

drift_dev/lib/src/analysis/dialect.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ base class ResolvedDialect {
1919
'mysql' || 'mariadb' => ResolvedDialect(SqlDialect.mariadb),
2020
'postgres' => ResolvedDialect(SqlDialect.postgres),
2121
'duckdb' => ResolvedDialect(SqlDialect.duckdb),
22+
// TODO: Support custom dialects?
2223
_ => throw ArgumentError.value(
2324
json['dialect'],
2425
'dialect',
@@ -31,6 +32,28 @@ base class ResolvedDialect {
3132

3233
void writeOptions(TextEmitter scope) {}
3334

35+
void writeDialectFactory(TextEmitter scope) {
36+
final dialectClass = switch (dialect) {
37+
SqlDialect.sqlite => scope.refUri(
38+
Uri.parse('package:drift_sqlite/drift_sqlite.dart'),
39+
'SqliteDialect',
40+
),
41+
SqlDialect.postgres => scope.refUri(
42+
Uri.parse(
43+
'package:drift_postgres/src/drift3_preview/drift_postgres.dart',
44+
),
45+
'PostgresDialect',
46+
),
47+
_ => throw UnsupportedError(
48+
'Drift3 does not support ${dialect.name} yet',
49+
),
50+
};
51+
52+
scope
53+
..write(dialectClass)
54+
..write('.new');
55+
}
56+
3457
Map<String, Object?> toJson() {
3558
return {'dialect': dialect.name};
3659
}

drift_dev/lib/src/cli/commands/schema/generate_utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class GenerateUtils {
128128
// can be referenced in snippets that are part of the schema files.
129129
writer.leaf()
130130
..writeln(generatedHeader)
131-
..writeln("import 'package:drift/drift.dart';");
131+
..writeln("import '${imports.driftUri}';");
132132

133133
final database = DriftDatabase(
134134
id: DriftElementId(SchemaReader.elementUri, 'database'),

drift_dev/lib/src/services/schema/schema_files.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
55
import 'package:drift/drift.dart' show DriftSqlType, SqlDialect, UpdateKind;
66
import 'package:drift_dev/src/analysis/resolver/drift/sqlparser/mapping.dart';
77
import 'package:logging/logging.dart';
8+
import 'package:meta/meta.dart';
89
import 'package:pub_semver/pub_semver.dart';
910
import 'package:recase/recase.dart';
1011
import 'package:sqlparser/sqlparser.dart' hide PrimaryKeyColumn, UniqueColumn;
@@ -53,7 +54,10 @@ class SchemaWriter {
5354
/// For this reason, we prefer to only export the `CREATE TABLE` statements
5455
/// that drift actually generated as a reference. We still support the older
5556
/// model, but the newer is much simpler while also being more reliable.
56-
Future<Map<String, Object?>> createSchemaJson({File? dumpStartupCode}) async {
57+
Future<Map<String, Object?>> createSchemaJson({
58+
File? dumpStartupCode,
59+
@visibleForTesting bool throwOnSchemaIsolateFailure = false,
60+
}) async {
5761
final knownStatements = <String, List<(SqlDialect, String)>>{};
5862
try {
5963
final statements = await SchemaIsolate.collectStatements(
@@ -69,6 +73,7 @@ class SchemaWriter {
6973
));
7074
}
7175
} on SchemaIsolateException catch (e) {
76+
if (throwOnSchemaIsolateFailure) rethrow;
7277
_logger.warning(e.description(isFatal: false));
7378
}
7479

drift_dev/lib/src/services/schema/schema_isolate.dart

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ class SchemaIsolate {
2929
static Future<String> generateStartupCode(
3030
SchemaIsolateOptions options,
3131
) async {
32+
final driftOptions = options.options;
3233
final imports = LibraryImportManager();
3334
final writer = Writer(
3435
DriftOptions.fromJson({
35-
...options.options.toJson(),
36+
...driftOptions.toJson(),
3637
'generate_manager': false,
3738
'skip_verification_code': true,
3839
'data_class_to_companions': false,
39-
if (!options.options.drift3Preview)
40+
if (!driftOptions.drift3Preview)
4041
'sql': {
4142
'dialects': switch (options.dialect) {
4243
null => SqlDialect.values.map((e) => e.name).toList(),
@@ -67,17 +68,31 @@ class SchemaIsolate {
6768
final isolate = Uri.parse('dart:isolate');
6869
final schemaTools = Uri.parse('package:drift/internal/export_schema.dart');
6970

70-
writer.leaf()
71-
..writeln(
72-
'void main('
73-
'${prefixed(core, 'List')}<${prefixed(core, 'String')}> args, '
74-
'${prefixed(isolate, 'SendPort')} port) {',
75-
)
76-
..writeln(
71+
final main = writer.leaf();
72+
main.writeln(
73+
'void main('
74+
'${prefixed(core, 'List')}<${prefixed(core, 'String')}> args, '
75+
'${prefixed(isolate, 'SendPort')} port) {',
76+
);
77+
78+
if (driftOptions.drift3Preview) {
79+
main.write(
80+
'${prefixed(schemaTools, 'sendCreateStatements')}'
81+
'(args, port, DatabaseAtV1.new, [',
82+
);
83+
for (final dialect in driftOptions.drift3Dialects) {
84+
dialect.writeDialectFactory(main);
85+
main.writeln(',');
86+
}
87+
main.writeln(']);');
88+
} else {
89+
main.writeln(
7790
'${prefixed(schemaTools, 'SchemaExporter')}'
7891
'.run(args, port, DatabaseAtV1.new);',
79-
)
80-
..writeln('}');
92+
);
93+
}
94+
95+
main.writeln('}');
8196

8297
final database = DriftDatabase(
8398
id: DriftElementId(SchemaReader.elementUri, 'database'),
@@ -134,6 +149,7 @@ class SchemaIsolate {
134149
errorsAreFatal: true,
135150
onError: receiveErrors.sendPort,
136151
packageConfig: await Isolate.packageConfig,
152+
debugName: 'drift schema export',
137153
);
138154
} catch (e) {
139155
throw SchemaIsolateException(e, entrypointFile);

drift_dev/lib/src/writer/import_manager.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ class LibraryImportManager implements ImportManager {
9292

9393
LibraryImportManager([this._outputUri]);
9494

95+
bool get _isDrift3 => emitter?.writer.options.drift3Preview ?? false;
96+
97+
String get driftUri =>
98+
_isDrift3 ? 'package:drift3/drift.dart' : 'package:drift/drift.dart';
99+
95100
void enforceAlias(Uri uri, String? alias) {
96101
_importAliases[uri] ??= alias;
97102
}
@@ -120,6 +125,14 @@ class LibraryImportManager implements ImportManager {
120125
definitionUri.path,
121126
from: url.dirname(_outputUri!.path),
122127
);
128+
} else if (_isDrift3 &&
129+
importedScheme == 'package' &&
130+
definitionUri.path.startsWith('drift/')) {
131+
final originalLibrary = url.relative(
132+
definitionUri.path,
133+
from: 'drift/',
134+
);
135+
importLiteral = 'package:drift3/$originalLibrary';
123136
} else {
124137
importLiteral = definitionUri.toString();
125138
}

drift_dev/lib/src/writer/tables/table_writer.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -641,15 +641,17 @@ class TableWriter extends TableOrViewWriter {
641641
void writeTableInfoClass() {
642642
if (!scope.generationOptions.writeDataClasses) {
643643
// Write a small table header without data class
644+
final infoName = table.entityInfoName;
644645
buffer
645-
..write('class ${table.entityInfoName} extends ')
646+
..write('class $infoName extends ')
646647
..write(emitter.drift('Table'))
647648
..write(' with ');
648649
if (scope.drift3) {
649650
buffer
650651
..write(emitter.drift('ResultSet'))
651-
..write(' implements ')
652-
..write(emitter.drift('GeneratedTable'));
652+
..write('<Never, $infoName> implements ')
653+
..write(emitter.drift('GeneratedTable'))
654+
..write('<Never, $infoName>');
653655
} else {
654656
buffer.write(emitter.drift('TableInfo'));
655657
}

drift_dev/test/services/schema/writer_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,50 @@ class Database {}
564564
});
565565
}
566566
});
567+
568+
test('can run schema isolate for drift3 code', () async {
569+
const options = DriftOptions.defaults(drift3Preview: true);
570+
final backend = await TestBackend.inTest({
571+
'a|lib/main.dart': '''
572+
import 'package:drift3/drift.dart';
573+
574+
class Users extends Table {
575+
IntColumn get id => integer().autoIncrement();
576+
TextColumn get title => text();
577+
}
578+
579+
@DriftDatabase(tables: [Users])
580+
class Database {}
581+
''',
582+
}, options: options);
583+
584+
final file = await backend.analyze('package:a/main.dart');
585+
backend.expectNoErrors();
586+
final db = file.fileAnalysis!.resolvedDatabases.values.single;
587+
588+
final json = await SchemaWriter(
589+
db.availableElements,
590+
options: options,
591+
).createSchemaJson(throwOnSchemaIsolateFailure: true);
592+
593+
expect(
594+
json,
595+
containsPair('fixed_sql', [
596+
{
597+
'name': 'users',
598+
'sql': [
599+
{
600+
'dialect': 'sqlite',
601+
'sql':
602+
'CREATE TABLE "users" ('
603+
'"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,'
604+
'"title" TEXT NOT NULL);',
605+
},
606+
],
607+
},
608+
]),
609+
);
610+
});
567611
}
568612

569613
const expected = r'''

0 commit comments

Comments
 (0)