diff --git a/drift/lib/internal/export_schema.dart b/drift/lib/internal/export_schema.dart index 5cae5feb6..ab3e0c4c5 100644 --- a/drift/lib/internal/export_schema.dart +++ b/drift/lib/internal/export_schema.dart @@ -75,10 +75,8 @@ final class SchemaExporter { 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(); - final result = - await export._collect(dialects: dialects, elementNames: elements); + final result = await export._collect(dialects: dialects); final serialized = [ for (final row in result.collectedStatements) [row.element, row.dialect.name, row.stmt] diff --git a/drift_dev/CHANGELOG.md b/drift_dev/CHANGELOG.md index f0866278e..beb8b3e3b 100644 --- a/drift_dev/CHANGELOG.md +++ b/drift_dev/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.29.1-dev +- Schema export: Always export generated SQL statements as a reference. - Schema export: Always use temporary file instead of data URI. ## 2.29.0 diff --git a/drift_dev/lib/src/analysis/results/table.dart b/drift_dev/lib/src/analysis/results/table.dart index a83e37088..5e0675fee 100644 --- a/drift_dev/lib/src/analysis/results/table.dart +++ b/drift_dev/lib/src/analysis/results/table.dart @@ -28,13 +28,13 @@ class DriftTable extends DriftElementWithResultSet { final List interfacesForRowClass; /// The fixed [entityInfoName] to use, overriding the default. - final String? fixedEntityInfoName; + String? fixedEntityInfoName; /// The default name to use for the [entityInfoName]. - final String baseDartName; + String baseDartName; @override - final String nameOfRowClass; + String nameOfRowClass; @override final String? nameOfCompanionClass; diff --git a/drift_dev/lib/src/analysis/results/view.dart b/drift_dev/lib/src/analysis/results/view.dart index 5125452a1..4175fdeac 100644 --- a/drift_dev/lib/src/analysis/results/view.dart +++ b/drift_dev/lib/src/analysis/results/view.dart @@ -26,7 +26,7 @@ class DriftView extends DriftElementWithResultSet { ExistingRowClass? existingRowClass; @override - final String nameOfRowClass; + String nameOfRowClass; @override final String? nameOfCompanionClass; diff --git a/drift_dev/lib/src/cli/commands/make_migrations.dart b/drift_dev/lib/src/cli/commands/make_migrations.dart index 730959260..6ed56151f 100644 --- a/drift_dev/lib/src/cli/commands/make_migrations.dart +++ b/drift_dev/lib/src/cli/commands/make_migrations.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:drift_dev/src/analysis/results/results.dart'; @@ -321,7 +320,7 @@ class _MigrationTestEmitter { final writer = SchemaWriter(driftElements, options: cli.project.options); final schemaFile = driftSchemaFile(schemaVersion); - final content = json.encode(await writer.createSchemaJson( + final content = SchemaWriter.json.convert(await writer.createSchemaJson( dumpStartupCode: dumpGeneratedSchemaCode)); if (!schemaFile.existsSync()) { cli.logger diff --git a/drift_dev/lib/src/cli/commands/schema.dart b/drift_dev/lib/src/cli/commands/schema.dart index a6147b4d5..deaf39c2a 100644 --- a/drift_dev/lib/src/cli/commands/schema.dart +++ b/drift_dev/lib/src/cli/commands/schema.dart @@ -92,7 +92,7 @@ Future> parseSchema(Directory directory) async { final version = int.parse(match.group(1)!); final rawData = json.decode(await entity.readAsString()); - final schema = SchemaReader.readJson(rawData as Map); + final schema = await SchemaReader.readJson(rawData as Map); results[version] = ExportedSchema(schema.entities.toList(), schema.options); } diff --git a/drift_dev/lib/src/cli/commands/schema/dump.dart b/drift_dev/lib/src/cli/commands/schema/dump.dart index 621e1acdf..e09f9810a 100644 --- a/drift_dev/lib/src/cli/commands/schema/dump.dart +++ b/drift_dev/lib/src/cli/commands/schema/dump.dart @@ -78,7 +78,7 @@ class DumpSchemaCommand extends Command { await parent.create(recursive: true); } - await file.writeAsString(json.encode( + await file.writeAsString(SchemaWriter.json.convert( await writer.createSchemaJson(dumpStartupCode: dumpSchemaCode))); print('Wrote to $target'); } diff --git a/drift_dev/lib/src/services/schema/schema_files.dart b/drift_dev/lib/src/services/schema/schema_files.dart index 7ef0a5621..db589d3ce 100644 --- a/drift_dev/lib/src/services/schema/schema_files.dart +++ b/drift_dev/lib/src/services/schema/schema_files.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:collection/collection.dart'; @@ -13,10 +14,11 @@ import '../../analysis/resolver/shared/data_class.dart'; import '../../analysis/results/results.dart'; import '../../writer/utils/column_constraints.dart'; import 'schema_isolate.dart'; +import 'sqlite_to_drift.dart'; class _ExportedSchemaVersion { - static final Version current = _supportDialectSpecificConstraints; - static final Version _supportDialectSpecificConstraints = Version(1, 2, 0); + static final Version current = _exportSql; + static final Version _exportSql = Version(1, 3, 0); static final Version _supportDartIndex = Version(1, 1, 0); final Version version; @@ -43,56 +45,30 @@ class SchemaWriter { /// Exports analyzed drift elements into a serialized format that can be used /// to re-construct the current database schema later. /// - /// Some drift elements, in particular Dart-defined views, are partially - /// defined at runtime and require running code. To infer the schema of these - /// elements, this method runs drift's code generator and spawns up a short- - /// lived isolate to collect the actual `CREATE` statements generated at - /// runtime. + /// Older versions of drift only exported schema information based on drift's + /// element model. This is sometimes inaccurate or incomplete, since generated + /// `CREATE` statements can depend on runtime code (e.g. for views where the + /// select statement is composed through drift's query builder). + /// + /// For this reason, we prefer to only export the `CREATE TABLE` statements + /// that drift actually generated as a reference. We still support the older + /// model, but the newer is much simpler while also being more reliable. Future> createSchemaJson({File? dumpStartupCode}) async { - final requiresRuntimeInformation = []; - for (final element in elements) { - switch (element) { - case DriftTable(): - for (final column in element.columns) { - if (column.sqlType is ColumnCustomType) { - requiresRuntimeInformation.add(element); - continue; - } - - if (column.defaultArgument != null) { - // This is an arbitrary Dart expression allowed to contain user - // code. To make sure the schema file stays valid, evaluate it - // once now and replace the expression with the result as a - // constant when serializing. - requiresRuntimeInformation.add(element); - continue; - } - } - case DriftView(): - if (element.source is! SqlViewSource) { - requiresRuntimeInformation.add(element); - } - } - } - final knownStatements = >{}; - if (requiresRuntimeInformation.isNotEmpty) { - try { - final statements = await SchemaIsolate.collectStatements( - options: options, - allElements: elements, - elementFilter: requiresRuntimeInformation, - dumpStartupCode: dumpStartupCode, - ); + try { + final statements = await SchemaIsolate.collectStatements( + options: options, + allElements: elements, + dumpStartupCode: dumpStartupCode, + ); - for (final statement in statements) { - knownStatements - .putIfAbsent(statement.elementName, () => []) - .add((statement.dialect, statement.createStatement)); - } - } on SchemaIsolateException catch (e) { - _logger.warning(e.description(isFatal: false)); + for (final statement in statements) { + knownStatements + .putIfAbsent(statement.elementName, () => []) + .add((statement.dialect, statement.createStatement)); } + } on SchemaIsolateException catch (e) { + _logger.warning(e.description(isFatal: false)); } return { @@ -103,9 +79,24 @@ class SchemaWriter { }, 'options': _serializeOptions(), 'entities': elements - .map((e) => _entityToJson(e, knownStatements)) + .map((e) => _entityToJson(e, + e is DriftSchemaElement ? knownStatements[e.schemaName] : null)) .whereType() .toList(), + if (knownStatements.isNotEmpty) + 'fixed_sql': [ + for (final MapEntry(:key, :value) in knownStatements.entries) + { + 'name': key, + 'sql': [ + for (final (dialect, sql) in value) + { + 'dialect': dialect.name, + 'sql': sql, + } + ] + } + ], }; } @@ -117,8 +108,8 @@ class SchemaWriter { return asJson; } - Map? _entityToJson(DriftElement entity, - Map> knownStatements) { + Map? _entityToJson( + DriftElement entity, List<(SqlDialect, String)>? knownSql) { String? type; Map? data; @@ -132,8 +123,8 @@ class SchemaWriter { // a different thing when dependencies are changed, while we want an // immutable schema snapshot. CreateTableStatement? actualTable; - if (knownStatements[entity.schemaName] case final known?) { - final sql = known.firstWhere((e) => e.$1 == SqlDialect.sqlite).$2; + if (knownSql != null) { + final sql = knownSql.firstWhere((e) => e.$1 == SqlDialect.sqlite).$2; final engine = SqlEngine(EngineOptions(version: SqliteVersion.current)); final result = engine.parse(sql); @@ -171,8 +162,8 @@ class SchemaWriter { }; } else if (entity is DriftView) { String? sql; - if (knownStatements[entity.schemaName] case final known?) { - sql = known.firstWhere((e) => e.$1 == SqlDialect.sqlite).$2; + if (knownSql != null) { + sql = knownSql.firstWhere((e) => e.$1 == SqlDialect.sqlite).$2; } else { final source = entity.source; if (source is! SqlViewSource) { @@ -340,6 +331,9 @@ class SchemaWriter { } static final _logger = Logger('drift_dev.SchemaWriter'); + + /// A suitable JSON encoder for emitting schema export JSONs. + static const json = JsonEncoder.withIndent(' '); } /// Reads files generated by [SchemaWriter]. @@ -359,13 +353,15 @@ class SchemaReader { SchemaReader._(); - factory SchemaReader.readJson(Map json) { - return SchemaReader._().._read(json); + static Future readJson(Map json) async { + final reader = SchemaReader._(); + await reader._read(json); + return reader; } Iterable get entities => _entitiesById.values; - void _read(Map json) { + Future _read(Map json) async { final meta = json['_meta'] as Map; _version = _ExportedSchemaVersion(Version.parse(meta['version'] as String)); @@ -375,9 +371,15 @@ class SchemaReader { { 'store_date_time_values_as_text': false, }; - final entities = json['entities'] as List; + if (json['fixed_sql'] case List fixedSql) { + // If we have access to actual CREATE statements drift used to generate at + // the version the schema was created, that is much better than + // reconstructing from element exports. Do that. + return _processFromSql(fixedSql, entities); + } + for (final raw in entities) { final rawData = raw as Map; final id = rawData['id'] as int; @@ -397,6 +399,48 @@ class SchemaReader { DriftDeclaration get _declaration => DriftDeclaration(elementUri, -1, ''); + Future _processFromSql( + List fixedSql, List entities) async { + final sql = []; + + for (final entry in fixedSql) { + for (final dialect in (entry as Map)['sql'] as List) { + if ((dialect as Map)['dialect'] == SqlDialect.sqlite.name) { + sql.add(dialect['sql'] as String); + } + } + } + + final elements = await extractDriftElementsFromSql(sql); + for (final (i, element) in elements.indexed) { + if (element is DriftTable) { + final pascalCase = ReCase(element.id.name).pascalCase; + // Mirror the naming scheme from _readTable when reading from SQL. + element + ..baseDartName = pascalCase + ..fixedEntityInfoName = pascalCase + ..nameOfRowClass = '${pascalCase}Data'; + } else if (element is DriftView) { + for (final entity in entities.cast>()) { + if (entity['type'] == 'view') { + final data = entity['data'] as Map; + if (data['name'] == element.schemaName) { + final entityInfoName = data['dart_info_name'] as String; + + element + ..entityInfoName = data['dart_info_name'] as String + ..nameOfRowClass = dataClassNameForClassName(entityInfoName); + + break; + } + } + } + } + + _entitiesById[i] = element; + } + } + void _processById(int id) { if (_entitiesById.containsKey(id)) return; if (_currentlyProcessing.contains(id)) { diff --git a/drift_dev/lib/src/services/schema/schema_isolate.dart b/drift_dev/lib/src/services/schema/schema_isolate.dart index 16bf7e54e..818c3bdce 100644 --- a/drift_dev/lib/src/services/schema/schema_isolate.dart +++ b/drift_dev/lib/src/services/schema/schema_isolate.dart @@ -159,7 +159,6 @@ class SchemaIsolate { static Future> collectStatements({ required DriftOptions options, required List allElements, - required List elementFilter, File? dumpStartupCode, }) async { final result = await _startAndRun(( @@ -171,9 +170,8 @@ class SchemaIsolate { 'v2', json.encode({ 'dialects': [ - for (final dialect in SqlDialect.values) dialect.name, + for (final dialect in options.supportedDialects) dialect.name, ], - 'elements': [for (final element in elementFilter) element.schemaName] }) ]); diff --git a/drift_dev/lib/src/services/schema/sqlite_to_drift.dart b/drift_dev/lib/src/services/schema/sqlite_to_drift.dart index 9b4f2f3dd..8697d9128 100644 --- a/drift_dev/lib/src/services/schema/sqlite_to_drift.dart +++ b/drift_dev/lib/src/services/schema/sqlite_to_drift.dart @@ -18,7 +18,29 @@ import 'verifier_common.dart'; Future> extractDriftElementsFromDatabase( CommonDatabase database) async { // Put everything from sqlite_schema into a fake drift file, analyze it. - final logger = Logger('extractDriftElementsFromDatabase'); + final createStatements = []; + for (final row in database.select('select * from sqlite_master')) { + final name = row['name'] as String?; + var sql = row['sql'] as String?; + + if (name == null || sql == null) { + continue; + } + + if (!sql.endsWith(';')) { + sql += ';'; + } + + createStatements.add(sql); + } + + return await extractDriftElementsFromSql(createStatements); +} + +Future> extractDriftElementsFromSql( + List nameToCreateStatements) async { + // Put all create statements into a fake file, then analyze it. + final logger = Logger('extractDriftElementsFromSql'); final uri = Uri.parse('db.drift'); final backend = _SingleFileNoAnalyzerBackend(logger, uri); final driver = DriftAnalysisDriver( @@ -34,16 +56,7 @@ Future> extractDriftElementsFromDatabase( final engineForParsing = driver.newSqlEngine(); final entities = {}; final virtualTableNames = []; - for (final row in database.select('select * from sqlite_master')) { - final name = row['name'] as String?; - var sql = row['sql'] as String?; - - if (name == null || - sql == null || - isInternalElement(name, virtualTableNames)) { - continue; - } - + for (var sql in nameToCreateStatements) { if (!sql.endsWith(';')) { sql += ';'; } @@ -57,7 +70,13 @@ Future> extractDriftElementsFromDatabase( virtualTableNames.add(parsed.tableName); } - entities[name] = sql; + if (parsed is CreatingStatement) { + if (!isInternalElement(parsed.createdName, virtualTableNames)) { + entities[parsed.createdName] = sql; + } + } else { + entities['atCreate_${entities.length}'] = '@create: $sql'; + } } entities.removeWhere((name, _) => isInternalElement(name, virtualTableNames)); backend.contents = entities.values.join('\n'); diff --git a/drift_dev/test/cli/schema/dump_test.dart b/drift_dev/test/cli/schema/dump_test.dart index 242e7f2aa..ee1a5a889 100644 --- a/drift_dev/test/cli/schema/dump_test.dart +++ b/drift_dev/test/cli/schema/dump_test.dart @@ -34,7 +34,7 @@ void main() { { "_meta": { "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.2.0" + "version": "1.3.0" }, "options": { "store_date_time_values_as_text": false @@ -85,7 +85,7 @@ void main() { "type": "view", "data": { "name": "names", - "sql": "CREATE VIEW names AS SELECT name FROM users;", + "sql": "CREATE VIEW names AS SELECT name FROM users", "dart_info_name": "Names", "columns": [ { @@ -130,6 +130,34 @@ void main() { "columns": [] } } + ], + "fixed_sql": [ + { + "name": "users", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \\"users\\" (\\"id\\" INTEGER PRIMARY KEY, \\"name\\" TEXT) STRICT;" + } + ] + }, + { + "name": "names", + "sql": [{"dialect": "sqlite", "sql": "CREATE VIEW names AS SELECT name FROM users"}] + }, + { + "name": "to_upper", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER to_upper AFTER UPDATE ON users BEGIN UPDATE users SET name = upper(new.name) WHERE id = new.id;END" + } + ] + }, + { + "name": "idx", + "sql": [{"dialect": "sqlite", "sql": "CREATE INDEX idx ON users (name)"}] + } ] } '''), diff --git a/drift_dev/test/services/schema/reader_test.dart b/drift_dev/test/services/schema/reader_test.dart index c010402be..84ec2504c 100644 --- a/drift_dev/test/services/schema/reader_test.dart +++ b/drift_dev/test/services/schema/reader_test.dart @@ -47,7 +47,7 @@ CREATE TABLE users ( }); test('can read old index format', () async { - final reader = SchemaReader.readJson( + final reader = await SchemaReader.readJson( json.decode(''' { "_meta": { @@ -123,6 +123,6 @@ Future> _analyzeAndSerialize(String source) async { final schemaJson = json.decode(json.encode(await writer.createSchemaJson())); final deserialized = - SchemaReader.readJson(schemaJson as Map); + await SchemaReader.readJson(schemaJson as Map); return deserialized.entities.toList(); } diff --git a/drift_dev/test/services/schema/writer_test.dart b/drift_dev/test/services/schema/writer_test.dart index 6915dc0b4..8c148a83a 100644 --- a/drift_dev/test/services/schema/writer_test.dart +++ b/drift_dev/test/services/schema/writer_test.dart @@ -126,32 +126,48 @@ class Database {} schemaWithOptions['options'], {'store_date_time_values_as_text': true}); }); - test('can generate code from schema json', () async { - final serializedSchema = json.decode( + group('can generate code from schema json', () { + for (final withFixedSql in [false, true]) { + test(withFixedSql ? 'with fixed sql' : 'without fixed sql', () async { + var serializedSchema = json.decode( // Column types used to be serialized under a different format, test // reading that as well. - expected.replaceAll('"int"', '"ColumnType.integer"')) - as Map; - final reader = SchemaReader.readJson(serializedSchema); + expected.replaceAll( + '"int"', '"ColumnType.integer"')) as Map; + serializedSchema = Map.of(serializedSchema); + if (!withFixedSql) { + serializedSchema.remove('fixed_sql'); + } - final fakeBuildConfig = runInBuildConfigZone(() { - return BuildConfig(buildTargets: {}); - }, 'drift_dev', []); - final generated = await GenerateUtils.generateSchemaCode( - DriftDevCli() - ..project = DriftProject(fakeBuildConfig, Directory(sandbox)), - 1, - ExportedSchema(reader.entities.toList(), {}), - false, - false, - ); + final reader = await SchemaReader.readJson(serializedSchema); - expect(generated, - contains('ComparableExpr(settings.length).isBiggerThanValue(10)')); - expect( - generated, - contains( - "OnCreateQuery('INSERT INTO \"groups\" (name) VALUES (\\'test\\')')")); + final fakeBuildConfig = runInBuildConfigZone(() { + return BuildConfig(buildTargets: {}); + }, 'drift_dev', []); + final generated = await GenerateUtils.generateSchemaCode( + DriftDevCli() + ..project = DriftProject(fakeBuildConfig, Directory(sandbox)), + 1, + ExportedSchema(reader.entities.toList(), {}), + false, + false, + ); + + if (withFixedSql) { + expect(generated, contains('CHECK (LENGTH(setting) > 10)')); + } else { + expect( + generated, + contains( + 'ComparableExpr(settings.length).isBiggerThanValue(10)')); + } + + expect( + generated, + contains( + "OnCreateQuery('INSERT INTO \"groups\" (name) VALUES (\\'test\\')')")); + }); + } }); test('can export Dart-defined views', () async { @@ -204,7 +220,7 @@ class Database {} ])), }); - final reader = SchemaReader.readJson(schemaJson); + final reader = await SchemaReader.readJson(schemaJson); final fakeBuildConfig = runInBuildConfigZone(() { return BuildConfig(buildTargets: {}); }, 'drift_dev', []); @@ -245,6 +261,9 @@ class Database {} final schemaJson = await SchemaWriter(db.availableElements).createSchemaJson(); + // Force testing with old format that doesn't have the actual SQL statements + // yet. + schemaJson.remove('fixed_sql'); final serializedIndex = (schemaJson['entities'] as List)[1]; expect(serializedIndex['data'], { @@ -264,7 +283,8 @@ class Database {} oldFormat['entities'][1]['data']['columns'] = ['name']; for (final format in [oldFormat, newFormat]) { - final reader = SchemaReader.readJson(format as Map); + final reader = + await SchemaReader.readJson(format as Map); final fakeBuildConfig = runInBuildConfigZone(() { return BuildConfig(buildTargets: {}); }, 'drift_dev', []); @@ -340,355 +360,527 @@ class Database {} await runTest(true, 'CURRENT_TIMESTAMP'); }); }); + + test('merges SQL for @create queries', () async { + final options = DriftOptions.defaults(); + final backend = await TestBackend.inTest( + { + 'a|lib/main.drift': ''' +CREATE TABLE foo (bar TEXT); + +@create: INSERT INTO foo DEFAULT VALUES; +@create: UPDATE foo SET bar = 'testing'; +''', + }, + ); + + final file = await backend.analyze('package:a/main.drift'); + backend.expectNoErrors(); + + final schemaJson = + await SchemaWriter(file.analyzedElements.toList(), options: options) + .createSchemaJson(); + final reader = await SchemaReader.readJson(schemaJson); + final fakeBuildConfig = runInBuildConfigZone(() { + return BuildConfig(buildTargets: {}); + }, 'drift_dev', []); + final generated = await GenerateUtils.generateSchemaCode( + DriftDevCli() + ..project = DriftProject(fakeBuildConfig, Directory(sandbox)), + 1, + ExportedSchema(reader.entities.toList(), {}), + false, + false, + ); + + expect(generated, contains('INSERT INTO foo DEFAULT VALUES')); + expect(generated, contains("UPDATE foo SET bar = \\'testing\\'")); + }); } const expected = r''' { - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.2.0" - }, - "options": { - "store_date_time_values_as_text": false - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "groups", - "was_declared_in_moor": true, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": "NOT NULL PRIMARY KEY AUTOINCREMENT", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - "auto-increment" - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": "NOT NULL", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name2", - "getter_name": "name2", - "moor_type": "string", - "nullable": false, - "customConstraints": "NOT NULL GENERATED ALWAYS AS (upper(name)) VIRTUAL", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "generated_as": { - "dart_expression": { - "elements": [ - "const ", - { - "lexeme": "CustomExpression", - "import_uri": "package:drift/drift.dart" - }, - "('upper(name)')" - ] - }, - "stored": false - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": false, - "constraints": [ - "UNIQUE(name)" - ], - "unique_keys": [ - [ - "name" + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.3.0" + }, + "options": { + "store_date_time_values_as_text": false + }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "groups", + "was_declared_in_moor": true, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL PRIMARY KEY AUTOINCREMENT", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "auto-increment" + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "name2", + "getter_name": "name2", + "moor_type": "string", + "nullable": false, + "customConstraints": "NOT NULL GENERATED ALWAYS AS (upper(name)) VIRTUAL", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "generated_as": { + "dart_expression": { + "elements": [ + "const ", + { + "lexeme": "CustomExpression", + "import_uri": "package:drift/drift.dart" + }, + "('upper(name)')" ] - ] - } - }, - { - "id": 1, - "references": [], - "type": "table", - "data": { - "name": "email", - "was_declared_in_moor": true, - "columns": [ - { - "name": "sender", - "getter_name": "sender", - "moor_type": "string", - "nullable": false, - "customConstraints": "", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "title", - "getter_name": "title", - "moor_type": "string", - "nullable": false, - "customConstraints": "", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "body", - "getter_name": "body", - "moor_type": "string", - "nullable": false, - "customConstraints": "", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": true, - "create_virtual_stmt": "CREATE VIRTUAL TABLE \"email\" USING fts5(sender, title, body)", - "without_rowid": false, - "constraints": [] - } - }, - { - "id": 2, - "references": [], - "type": "table", - "data": { - "name": "users", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", - "dialectAwareDefaultConstraints": { - "sqlite": "PRIMARY KEY AUTOINCREMENT", - "postgres": "PRIMARY KEY AUTOINCREMENT" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - "auto-increment" - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "setting", - "getter_name": "settings", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'foobar\\'')", - "default_client_dart": null, - "dsl_features": [ - { - "check": { - "dart_expression": { - "elements": [ - { - "lexeme": "ComparableExpr", - "import_uri": "package:drift/src/runtime/query_builder/query_builder.dart" - }, - "(", - {"lexeme": "settings", "tag": "settings"}, - ".length).isBiggerThanValue(10)" - ] - } - } - } - ], - "type_converter": { - "dart_expr": "const SettingsConverter()", - "dart_type_name": "Settings" - } - } - ], - "is_virtual": false, - "without_rowid": false, - "constraints": [], - "unique_keys": [ - [ - "name", - "setting" - ] - ] - } - }, - { - "id": 3, - "references": [ - 0, - 2 - ], - "type": "table", - "data": { - "name": "group_members", - "was_declared_in_moor": true, - "columns": [ - { - "name": "group", - "getter_name": "group", - "moor_type": "int", - "nullable": false, - "customConstraints": "NOT NULL REFERENCES \"groups\"(id)", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": {"table": "groups", "column": "id"}, - "initially_deferred": false, - "on_update": null, - "on_delete": null - } - } - ] - }, - { - "name": "user", - "getter_name": "user", - "moor_type": "int", - "nullable": false, - "customConstraints": "NOT NULL REFERENCES users(id)", - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": {"table": "users", "column": "id"}, - "initially_deferred": false, - "on_update": null, - "on_delete": null - } - } - ] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": "NOT NULL DEFAULT FALSE", - "default_dart": "const CustomExpression('FALSE')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": false, - "constraints": [ - "PRIMARY KEY(\"group\", user)ON CONFLICT REPLACE" - ], - "explicit_pk": [ - "group", - "user" - ] - } - }, - { - "id": 4, - "references": [ - 3, - 0 - ], - "type": "trigger", - "data": { - "on": 3, - "references_in_body": [ - 3, - 0 - ], - "name": "delete_empty_groups", - "sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN\n DELETE FROM \"groups\"\n WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);\nEND;" - } - }, - { - "id": 5, - "references": [ - 0 - ], - "type": "index", - "data": { - "on": 0, - "name": "groups_name", - "sql": "CREATE INDEX groups_name ON \"groups\"(name, upper(name));", - "unique": false, - "columns": [] - } - }, - { - "id": 6, - "references": [ - 0 - ], - "type": "view", - "data": { - "name": "my_view", - "sql": "CREATE VIEW my_view AS SELECT id FROM \"groups\";", - "dart_info_name": "MyView", - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ] - } - }, - { - "id": 7, - "references": [6, 0], - "type": "trigger", - "data": { - "on": 6, - "references_in_body": [6, 0], - "name": "my_view_trigger", - "sql": "CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN\n UPDATE \"groups\" SET id = old.id;\nEND;" - } - }, - { - "id": 8, - "references": [0], - "type": "special-query", - "data": { - "scenario": "create", - "sql": "INSERT INTO \"groups\" (\"name\") VALUES ('test')" + }, + "stored": false + } + } + ] } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [ + "UNIQUE(name)" + ], + "unique_keys": [ + [ + "name" + ] + ] + } + }, + { + "id": 1, + "references": [], + "type": "table", + "data": { + "name": "email", + "was_declared_in_moor": true, + "columns": [ + { + "name": "sender", + "getter_name": "sender", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "title", + "getter_name": "title", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "body", + "getter_name": "body", + "moor_type": "string", + "nullable": false, + "customConstraints": "", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": true, + "create_virtual_stmt": "CREATE VIRTUAL TABLE \"email\" USING fts5(sender, title, body)", + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 2, + "references": [], + "type": "table", + "data": { + "name": "users", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT", + "postgres": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + "auto-increment" + ] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "setting", + "getter_name": "settings", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const CustomExpression('\\'foobar\\'')", + "default_client_dart": null, + "dsl_features": [ + { + "check": { + "dart_expression": { + "elements": [ + { + "lexeme": "ComparableExpr", + "import_uri": "package:drift/src/runtime/query_builder/query_builder.dart" + }, + "(", + { + "lexeme": "settings", + "tag": "settings" + }, + ".length).isBiggerThanValue(10)" + ] + } + } + } + ], + "type_converter": { + "dart_expr": "const SettingsConverter()", + "dart_type_name": "Settings" + } + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [], + "unique_keys": [ + [ + "name", + "setting" + ] + ] + } + }, + { + "id": 3, + "references": [ + 0, + 2 + ], + "type": "table", + "data": { + "name": "group_members", + "was_declared_in_moor": true, + "columns": [ + { + "name": "group", + "getter_name": "group", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL REFERENCES \"groups\"(id)", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "groups", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": null + } + } + ] + }, + { + "name": "user", + "getter_name": "user", + "moor_type": "int", + "nullable": false, + "customConstraints": "NOT NULL REFERENCES users(id)", + "default_dart": null, + "default_client_dart": null, + "dsl_features": [ + { + "foreign_key": { + "to": { + "table": "users", + "column": "id" + }, + "initially_deferred": false, + "on_update": null, + "on_delete": null + } + } + ] + }, + { + "name": "is_admin", + "getter_name": "isAdmin", + "moor_type": "bool", + "nullable": false, + "customConstraints": "NOT NULL DEFAULT FALSE", + "default_dart": "const CustomExpression('FALSE')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [ + "PRIMARY KEY(\"group\", user)ON CONFLICT REPLACE" + ], + "explicit_pk": [ + "group", + "user" + ] + } + }, + { + "id": 4, + "references": [ + 3, + 0 + ], + "type": "trigger", + "data": { + "on": 3, + "references_in_body": [ + 3, + 0 + ], + "name": "delete_empty_groups", + "sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN\n DELETE FROM \"groups\"\n WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);\nEND;" + } + }, + { + "id": 5, + "references": [ + 0 + ], + "type": "index", + "data": { + "on": 0, + "name": "groups_name", + "sql": "CREATE INDEX groups_name ON \"groups\"(name, upper(name));", + "unique": false, + "columns": [] + } + }, + { + "id": 6, + "references": [ + 0 + ], + "type": "view", + "data": { + "name": "my_view", + "sql": "CREATE VIEW my_view AS SELECT id FROM \"groups\"", + "dart_info_name": "MyView", + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ] + } + }, + { + "id": 7, + "references": [ + 6, + 0 + ], + "type": "trigger", + "data": { + "on": 6, + "references_in_body": [ + 6, + 0 + ], + "name": "my_view_trigger", + "sql": "CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN\n UPDATE \"groups\" SET id = old.id;\nEND;" + } + }, + { + "id": 8, + "references": [ + 0 + ], + "type": "special-query", + "data": { + "scenario": "create", + "sql": "INSERT INTO \"groups\" (\"name\") VALUES ('test')" + } + } + ], + "fixed_sql": [ + { + "name": "groups", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"groups\" (\"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" TEXT NOT NULL, \"name2\" TEXT NOT NULL GENERATED ALWAYS AS (upper(name)) VIRTUAL, UNIQUE(name));" + }, + { + "dialect": "postgres", + "sql": "CREATE TABLE IF NOT EXISTS \"groups\" (\"id\" bigserial PRIMARY KEY NOT NULL NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" text NOT NULL, \"name2\" text NOT NULL GENERATED ALWAYS AS (upper(name)) VIRTUAL, UNIQUE(name));" } - ] + ] + }, + { + "name": "email", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE VIRTUAL TABLE IF NOT EXISTS \"email\" USING fts5(sender, title, body);" + }, + { + "dialect": "postgres", + "sql": "CREATE VIRTUAL TABLE IF NOT EXISTS \"email\" USING fts5(sender, title, body);" + } + ] + }, + { + "name": "users", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"users\" (\"id\" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"name\" TEXT NOT NULL, \"setting\" TEXT NOT NULL DEFAULT 'foobar' CHECK(LENGTH(\"setting\") > 10), UNIQUE (\"name\", \"setting\"));" + }, + { + "dialect": "postgres", + "sql": "CREATE TABLE IF NOT EXISTS \"users\" (\"id\" bigserial PRIMARY KEY NOT NULL, \"name\" text NOT NULL, \"setting\" text NOT NULL DEFAULT 'foobar' CHECK(LENGTH(\"setting\") > 10), UNIQUE (\"name\", \"setting\"));" + } + ] + }, + { + "name": "group_members", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TABLE IF NOT EXISTS \"group_members\" (\"group\" INTEGER NOT NULL REFERENCES \"groups\"(id), \"user\" INTEGER NOT NULL REFERENCES users(id), \"is_admin\" INTEGER NOT NULL DEFAULT FALSE, PRIMARY KEY(\"group\", user)ON CONFLICT REPLACE);" + }, + { + "dialect": "postgres", + "sql": "CREATE TABLE IF NOT EXISTS \"group_members\" (\"group\" bigint NOT NULL REFERENCES \"groups\"(id), \"user\" bigint NOT NULL REFERENCES users(id), \"is_admin\" boolean NOT NULL DEFAULT FALSE, PRIMARY KEY(\"group\", user)ON CONFLICT REPLACE);" + } + ] + }, + { + "name": "delete_empty_groups", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN DELETE FROM \"groups\" WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);END" + }, + { + "dialect": "postgres", + "sql": "CREATE TRIGGER delete_empty_groups AFTER DELETE ON group_members BEGIN DELETE FROM \"groups\" WHERE NOT EXISTS (SELECT * FROM group_members WHERE \"group\" = \"groups\".id);END" + } + ] + }, + { + "name": "groups_name", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE INDEX groups_name ON \"groups\" (name, upper(name))" + }, + { + "dialect": "postgres", + "sql": "CREATE INDEX groups_name ON \"groups\" (name, upper(name))" + } + ] + }, + { + "name": "my_view", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE VIEW my_view AS SELECT id FROM \"groups\"" + }, + { + "dialect": "postgres", + "sql": "CREATE VIEW my_view AS SELECT id FROM \"groups\"" + } + ] + }, + { + "name": "my_view_trigger", + "sql": [ + { + "dialect": "sqlite", + "sql": "CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN UPDATE \"groups\" SET id = old.id;END" + }, + { + "dialect": "postgres", + "sql": "CREATE TRIGGER my_view_trigger INSTEAD OF UPDATE ON my_view BEGIN UPDATE \"groups\" SET id = old.id;END" + } + ] + }, + { + "name": "$internal$", + "sql": [ + { + "dialect": "sqlite", + "sql": "INSERT INTO \"groups\" (name) VALUES ('test')" + }, + { + "dialect": "postgres", + "sql": "INSERT INTO \"groups\" (name) VALUES ('test')" + } + ] + } + ] } ''';