From 9eb15149d741cbd85f6609e6f35172f6215d5bc9 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Thu, 1 Sep 2022 23:10:54 +0200 Subject: [PATCH] Serialize drift analysis results --- drift_dev/drift_dev.build.yaml | 5 + .../lib/src/analysis/preprocess_drift.dart | 2 +- .../src/analysis/resolver/drift/table.dart | 29 +++- .../lib/src/analysis/results/column.dart | 93 +++++++++- drift_dev/lib/src/analysis/results/dart.dart | 30 +++- .../lib/src/analysis/results/element.dart | 19 +- drift_dev/lib/src/analysis/results/table.dart | 6 +- drift_dev/lib/src/analysis/serializer.dart | 163 ++++++++++++++++++ drift_dev/lib/src/analyzer/options.dart | 2 +- .../analysis/preprocess_drift.g.dart | 2 +- .../generated/analysis/results/column.g.dart | 68 ++++++++ .../generated/analysis/results/dart.g.dart | 31 ++++ .../generated/analysis/results/element.g.dart | 53 ++++++ .../{ => generated}/analyzer/options.g.dart | 2 +- drift_dev/pubspec.yaml | 2 +- .../analysis/resolver/drift/table_test.dart | 56 ++++++ 16 files changed, 552 insertions(+), 11 deletions(-) create mode 100644 drift_dev/lib/src/analysis/serializer.dart rename drift_dev/lib/src/{ => generated}/analysis/preprocess_drift.g.dart (97%) create mode 100644 drift_dev/lib/src/generated/analysis/results/column.g.dart create mode 100644 drift_dev/lib/src/generated/analysis/results/dart.g.dart create mode 100644 drift_dev/lib/src/generated/analysis/results/element.g.dart rename drift_dev/lib/src/{ => generated}/analyzer/options.g.dart (99%) create mode 100644 drift_dev/test/analysis/resolver/drift/table_test.dart diff --git a/drift_dev/drift_dev.build.yaml b/drift_dev/drift_dev.build.yaml index 58cf7643..b3d6e6b3 100644 --- a/drift_dev/drift_dev.build.yaml +++ b/drift_dev/drift_dev.build.yaml @@ -11,3 +11,8 @@ targets: disallow_unrecognized_keys: true field_rename: snake explicit_to_json: true + # https://simonbinder.eu/posts/build_directory_moves/#generating-into-a-directory-with-source_gen + source_gen:combining_builder: + options: + build_extensions: + '^lib/src/{{}}.dart': 'lib/src/generated/{{}}.g.dart' diff --git a/drift_dev/lib/src/analysis/preprocess_drift.dart b/drift_dev/lib/src/analysis/preprocess_drift.dart index 73724ed7..4e189759 100644 --- a/drift_dev/lib/src/analysis/preprocess_drift.dart +++ b/drift_dev/lib/src/analysis/preprocess_drift.dart @@ -5,7 +5,7 @@ import 'package:path/path.dart' show url; import '../utils/string_escaper.dart'; import 'backend.dart'; -part 'preprocess_drift.g.dart'; +part '../generated/analysis/preprocess_drift.g.dart'; @JsonSerializable(constructor: '_') class DriftPreprocessorResult { diff --git a/drift_dev/lib/src/analysis/resolver/drift/table.dart b/drift_dev/lib/src/analysis/resolver/drift/table.dart index 0dfcada6..c29e3a46 100644 --- a/drift_dev/lib/src/analysis/resolver/drift/table.dart +++ b/drift_dev/lib/src/analysis/resolver/drift/table.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart'; @@ -40,6 +41,7 @@ class DriftTableResolver extends LocalElementResolver { for (final column in table.resultColumns) { String? overriddenDartName; final type = column.type.sqlTypeToDrift(resolver.driver.options); + final constraints = []; for (final constraint in column.constraints) { if (constraint is DriftDartName) { @@ -60,6 +62,23 @@ class DriftTableResolver extends LocalElementResolver { if (referenced != null) { references.add(referenced); + + // Try to resolve this column to track the exact dependency. Don't + // report a warning if this fails, a separate lint step does that. + final columnName = + constraint.clause.columnNames.firstOrNull?.columnName; + if (columnName != null) { + final targetColumn = referenced.columns + .firstWhereOrNull((c) => c.hasEqualSqlName(columnName)); + + if (targetColumn != null) { + constraints.add(ForeignKeyReference( + targetColumn, + constraint.clause.onUpdate, + constraint.clause.onDelete, + )); + } + } } } } @@ -69,12 +88,20 @@ class DriftTableResolver extends LocalElementResolver { nullable: column.type.nullable != false, nameInSql: column.name, nameInDart: overriddenDartName ?? ReCase(column.name).camelCase, + constraints: constraints, + declaration: DriftDeclaration( + state.ownId.libraryUri, + column.definition!.nameToken!.span.start.offset, + ), )); } return DriftTable( discovered.ownId, - null, + DriftDeclaration( + state.ownId.libraryUri, + discovered.createTable.firstPosition, + ), columns: columns, references: references.toList(), ); diff --git a/drift_dev/lib/src/analysis/results/column.dart b/drift_dev/lib/src/analysis/results/column.dart index 07667f82..b96cd53a 100644 --- a/drift_dev/lib/src/analysis/results/column.dart +++ b/drift_dev/lib/src/analysis/results/column.dart @@ -1,8 +1,13 @@ import 'package:drift/drift.dart' show DriftSqlType; +import 'package:json_annotation/json_annotation.dart'; +import 'package:sqlparser/sqlparser.dart' show ReferenceAction; +import '../../analyzer/options.dart'; import 'dart.dart'; import 'element.dart'; +part '../../generated/analysis/results/column.g.dart'; + class DriftColumn { final DriftSqlType sqlType; @@ -13,23 +18,86 @@ class DriftColumn { /// constraint present on the column's definition. final bool nullable; + /// The (unescaped) name of this column in the database schema. final String nameInSql; + + /// The getter name of this column in the table class. It will also be used + /// as getter name in the TableInfo class (as it needs to override the field) + /// and in the generated data class that will be generated for each table. final String nameInDart; + /// The documentation comment associated with this column + /// + /// Stored as a multi line string with leading triple-slashes `///` for every + /// line. + final String? documentationComment; + + /// An (optional) name to use as a json key instead of the [nameInDart]. + final String? overriddenJsonName; + + /// Column constraints that should be applied to this column. + final List constraints; + + /// If this columns has custom constraints that should be used instead of the + /// default ones. + final String? customConstraints; + + /// The Dart code generating the default expression for this column (as an + /// `Expression` instance from `package:drift`). + final AnnotatedDartCode? defaultArgument; + + /// Dart code for the `clientDefault` expression, or null if it hasn't been + /// set. + final AnnotatedDartCode? clientDefaultCode; + final AppliedTypeConverter? typeConverter; - final DriftDeclaration? declaration; + final DriftDeclaration declaration; + + /// The table or view owning this column. + @JsonKey(ignore: true) + late DriftElement owner; DriftColumn({ required this.sqlType, required this.nullable, required this.nameInSql, required this.nameInDart, + required this.declaration, this.typeConverter, - this.declaration, + this.clientDefaultCode, + this.defaultArgument, + this.overriddenJsonName, + this.documentationComment, + this.constraints = const [], + this.customConstraints, }); + + /// Whether this column was declared inside a `.drift` file. + bool get declaredInDriftFile => declaration.isDriftDeclaration; + + /// The actual json key to use when serializing a data class of this table + /// to json. + /// + /// This respectts the [overriddenJsonName], if any, as well as [options]. + String getJsonKey([DriftOptions options = const DriftOptions.defaults()]) { + if (overriddenJsonName != null) return overriddenJsonName!; + + final useColumnName = options.useColumnNameAsJsonKeyWhenDefinedInMoorFile && + declaredInDriftFile; + return useColumnName ? nameInSql : nameInDart; + } + + bool hasEqualSqlName(String otherSqlName) => + nameInSql.toLowerCase() == otherSqlName.toLowerCase(); + + @override + String toString() { + return 'Column $nameInSql in $owner'; + } } +@JsonSerializable() class AppliedTypeConverter { /// The Dart expression creating an instance of the applied type converter. final AnnotatedDartCode expression; @@ -71,4 +139,25 @@ class AppliedTypeConverter { required this.sqlTypeIsNullable, this.alsoAppliesToJsonConversion = false, }); + + factory AppliedTypeConverter.fromJson(Map json) => + _$AppliedTypeConverterFromJson(json); + + Map toJson() => _$AppliedTypeConverterToJson(this); +} + +abstract class DriftColumnConstraint {} + +class ForeignKeyReference extends DriftColumnConstraint { + final DriftColumn otherColumn; + final ReferenceAction? onUpdate; + final ReferenceAction? onDelete; + + ForeignKeyReference(this.otherColumn, this.onUpdate, this.onDelete); + + @override + String toString() { + return 'ForeignKeyReference(to $otherColumn, onUpdate = $onUpdate, ' + 'onDelete = $onDelete)'; + } } diff --git a/drift_dev/lib/src/analysis/results/dart.dart b/drift_dev/lib/src/analysis/results/dart.dart index c115b8a3..eaebe5a3 100644 --- a/drift_dev/lib/src/analysis/results/dart.dart +++ b/drift_dev/lib/src/analysis/results/dart.dart @@ -1,14 +1,42 @@ +import 'package:json_annotation/json_annotation.dart'; + import 'element.dart'; +part '../../generated/analysis/results/dart.g.dart'; + class AnnotatedDartCode { - final List elements; + final List elements; AnnotatedDartCode(this.elements); + + factory AnnotatedDartCode.fromJson(Map json) { + final serializedElements = json['elements'] as List; + + return AnnotatedDartCode([ + for (final part in serializedElements) + if (part is Map) DartTopLevelSymbol.fromJson(json) else part as String + ]); + } + + Map toJson() { + return { + 'elements': [ + for (final element in elements) + if (element is DartTopLevelSymbol) element.toJson() else element + ], + }; + } } +@JsonSerializable() class DartTopLevelSymbol { final String lexeme; final DriftElementId elementId; DartTopLevelSymbol(this.lexeme, this.elementId); + + factory DartTopLevelSymbol.fromJson(Map json) => + _$DartTopLevelSymbolFromJson(json); + + Map toJson() => _$DartTopLevelSymbolToJson(this); } diff --git a/drift_dev/lib/src/analysis/results/element.dart b/drift_dev/lib/src/analysis/results/element.dart index c16bd3e3..cd992533 100644 --- a/drift_dev/lib/src/analysis/results/element.dart +++ b/drift_dev/lib/src/analysis/results/element.dart @@ -1,20 +1,28 @@ +import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' show url; import 'column.dart'; +part '../../generated/analysis/results/element.g.dart'; + @sealed +@JsonSerializable() class DriftElementId { final Uri libraryUri; final String name; DriftElementId(this.libraryUri, this.name); + factory DriftElementId.fromJson(Map json) => _$DriftElementIdFromJson(json); + bool get isDefinedInDart => url.extension(libraryUri.path) == '.dart'; bool get isDefinedInDrift => url.extension(libraryUri.path) == '.drift'; bool sameName(String name) => this.name.toLowerCase() == name.toLowerCase(); + Map toJson() => _$DriftElementIdToJson(this); + @override int get hashCode => Object.hash(DriftElementId, libraryUri, name); @@ -31,16 +39,25 @@ class DriftElementId { } } +@JsonSerializable() class DriftDeclaration { final Uri sourceUri; final int offset; DriftDeclaration(this.sourceUri, this.offset); + + factory DriftDeclaration.fromJson(Map json) => + _$DriftDeclarationFromJson(json); + + Map toJson() => _$DriftDeclarationToJson(this); + + bool get isDartDeclaration => url.extension(sourceUri.path) == '.dart'; + bool get isDriftDeclaration => url.extension(sourceUri.path) == '.drift'; } abstract class DriftElement { final DriftElementId id; - final DriftDeclaration? declaration; + final DriftDeclaration declaration; Iterable get references => const Iterable.empty(); diff --git a/drift_dev/lib/src/analysis/results/table.dart b/drift_dev/lib/src/analysis/results/table.dart index 0c259e44..c55114a0 100644 --- a/drift_dev/lib/src/analysis/results/table.dart +++ b/drift_dev/lib/src/analysis/results/table.dart @@ -14,5 +14,9 @@ class DriftTable extends DriftElementWithResultSet { super.declaration, { required this.columns, this.references = const [], - }); + }) { + for (final column in columns) { + column.owner = this; + } + } } diff --git a/drift_dev/lib/src/analysis/serializer.dart b/drift_dev/lib/src/analysis/serializer.dart new file mode 100644 index 00000000..edcd86ce --- /dev/null +++ b/drift_dev/lib/src/analysis/serializer.dart @@ -0,0 +1,163 @@ +import 'dart:convert' as convert; + +import 'package:drift/drift.dart' show DriftSqlType; +import 'package:drift_dev/src/analysis/results/dart.dart'; +import 'package:sqlparser/sqlparser.dart' show ReferenceAction; + +import 'results/column.dart'; +import 'results/element.dart'; +import 'results/table.dart'; + +class ElementSerializer { + Map serialize(DriftElement element) { + if (element is DriftTable) { + return { + 'type': 'table', + 'id': element.id.toJson(), + 'declaration': element.declaration.toJson(), + 'references': [ + for (final referenced in element.references) + _serializeElementReference(referenced), + ], + 'columns': [ + for (final column in element.columns) _serializeColumn(column), + ], + }; + } + + throw UnimplementedError('Unknown element $element'); + } + + Map _serializeColumn(DriftColumn column) { + return { + 'sqlType': column.sqlType.name, + 'nullable': column.nullable, + 'nameInSql': column.nameInSql, + 'nameInDart': column.nameInDart, + 'declaration': column.declaration.toJson(), + 'typeConverter': column.typeConverter?.toJson(), + 'clientDefaultCode': column.clientDefaultCode?.toJson(), + 'defaultArgument': column.clientDefaultCode?.toJson(), + 'overriddenJsonName': column.overriddenJsonName, + 'documentationComment': column.documentationComment, + 'constraints': [ + for (final constraint in column.constraints) + _serializeColumnConstraint(constraint), + ], + 'customConstraints': column.customConstraints, + }; + } + + Map _serializeColumnConstraint( + DriftColumnConstraint constraint) { + if (constraint is ForeignKeyReference) { + return { + 'type': 'foreign_key', + 'column': _serializeColumnReference(constraint.otherColumn), + 'onUpdate': constraint.onUpdate?.name, + 'onDelete': constraint.onDelete?.name, + }; + } else { + throw UnimplementedError('Unsupported column constrain: $constraint'); + } + } + + Map _serializeElementReference(DriftElement element) { + return element.id.toJson(); + } + + Map _serializeColumnReference(DriftColumn column) { + return { + 'table': _serializeElementReference(column.owner), + 'name': column.nameInSql, + }; + } +} + +abstract class ElementDeserializer { + final Map> _loadedJson = {}; + final Map _deserializedElements = {}; + + /// Loads the serialized definitions of all elements with a + /// [DriftElementId.libraryUri] matching the [uri]. + Future loadStateForUri(Uri uri); + + Future _readElementReference(Map json) async { + final id = DriftElementId.fromJson(json); + + final data = _loadedJson[id.libraryUri] ??= convert.json + .decode(await loadStateForUri(id.libraryUri)) as Map; + + return _deserializedElements[id] ??= + await _readDriftElement(data[id.name] as Map); + } + + Future _readDriftColumnReference(Map json) async { + final table = + (await _readElementReference(json['table'] as Map)) as DriftTable; + final name = json['name'] as String; + + return table.columns.singleWhere((c) => c.nameInSql == name); + } + + Future _readDriftElement(Map json) async { + final type = json['type'] as String; + final id = DriftElementId.fromJson(json['id'] as Map); + final declaration = DriftDeclaration.fromJson(json['declaration'] as Map); + + switch (type) { + case 'table': + return DriftTable(id, declaration, columns: [ + for (final rawColumn in json['columns'] as List) + await _readColumn(rawColumn as Map), + ]); + default: + throw UnimplementedError('Unsupported element type: $type'); + } + } + + Future _readColumn(Map json) async { + return DriftColumn( + sqlType: DriftSqlType.values.byName(json['sqlType'] as String), + nullable: json['nullable'] as bool, + nameInSql: json['nameInSql'] as String, + nameInDart: json['nameInDart'] as String, + declaration: DriftDeclaration.fromJson(json['declaration'] as Map), + typeConverter: json['typeConverter'] != null + ? AppliedTypeConverter.fromJson(json['typeConverter'] as Map) + : null, + clientDefaultCode: json['clientDefaultCode'] != null + ? AnnotatedDartCode.fromJson(json['clientDefaultCode'] as Map) + : null, + defaultArgument: json['defaultArgument'] != null + ? AnnotatedDartCode.fromJson(json['defaultArgument'] as Map) + : null, + overriddenJsonName: json['overriddenJsonName'] as String?, + documentationComment: json['documentationComment'] as String?, + constraints: [ + for (final rawConstraint in json['constraints'] as List) + await _readConstraint(rawConstraint as Map) + ], + customConstraints: json['customConstraints'] as String?, + ); + } + + Future _readConstraint(Map json) async { + final type = json['type'] as String; + + switch (type) { + case 'foreign_key': + ReferenceAction? readAction(String? value) { + return value == null ? null : ReferenceAction.values.byName(value); + } + + return ForeignKeyReference( + await _readDriftColumnReference(json['column'] as Map), + readAction(json['onUpdate'] as String?), + readAction(json['onDelete'] as String?), + ); + default: + throw UnimplementedError('Unsupported constraint: $type'); + } + } +} diff --git a/drift_dev/lib/src/analyzer/options.dart b/drift_dev/lib/src/analyzer/options.dart index b2559ff9..5f547e56 100644 --- a/drift_dev/lib/src/analyzer/options.dart +++ b/drift_dev/lib/src/analyzer/options.dart @@ -3,7 +3,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:sqlparser/sqlparser.dart' show SqliteVersion; -part 'options.g.dart'; +part '../generated/analyzer/options.g.dart'; /// Controllable options to define the behavior of the analyzer and the /// generator. diff --git a/drift_dev/lib/src/analysis/preprocess_drift.g.dart b/drift_dev/lib/src/generated/analysis/preprocess_drift.g.dart similarity index 97% rename from drift_dev/lib/src/analysis/preprocess_drift.g.dart rename to drift_dev/lib/src/generated/analysis/preprocess_drift.g.dart index d91774d5..65eef32a 100644 --- a/drift_dev/lib/src/analysis/preprocess_drift.g.dart +++ b/drift_dev/lib/src/generated/analysis/preprocess_drift.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'preprocess_drift.dart'; +part of '../../analysis/preprocess_drift.dart'; // ************************************************************************** // JsonSerializableGenerator diff --git a/drift_dev/lib/src/generated/analysis/results/column.g.dart b/drift_dev/lib/src/generated/analysis/results/column.g.dart new file mode 100644 index 00000000..527a1294 --- /dev/null +++ b/drift_dev/lib/src/generated/analysis/results/column.g.dart @@ -0,0 +1,68 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../../../analysis/results/column.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppliedTypeConverter _$AppliedTypeConverterFromJson(Map json) => $checkedCreate( + 'AppliedTypeConverter', + json, + ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const [ + 'expression', + 'dart_type', + 'sql_type', + 'dart_type_is_nullable', + 'sql_type_is_nullable', + 'also_applies_to_json_conversion' + ], + ); + final val = AppliedTypeConverter( + expression: $checkedConvert( + 'expression', (v) => AnnotatedDartCode.fromJson(v as Map)), + dartType: $checkedConvert( + 'dart_type', (v) => AnnotatedDartCode.fromJson(v as Map)), + sqlType: $checkedConvert( + 'sql_type', (v) => $enumDecode(_$DriftSqlTypeEnumMap, v)), + dartTypeIsNullable: + $checkedConvert('dart_type_is_nullable', (v) => v as bool), + sqlTypeIsNullable: + $checkedConvert('sql_type_is_nullable', (v) => v as bool), + alsoAppliesToJsonConversion: $checkedConvert( + 'also_applies_to_json_conversion', (v) => v as bool? ?? false), + ); + return val; + }, + fieldKeyMap: const { + 'dartType': 'dart_type', + 'sqlType': 'sql_type', + 'dartTypeIsNullable': 'dart_type_is_nullable', + 'sqlTypeIsNullable': 'sql_type_is_nullable', + 'alsoAppliesToJsonConversion': 'also_applies_to_json_conversion' + }, + ); + +Map _$AppliedTypeConverterToJson( + AppliedTypeConverter instance) => + { + 'expression': instance.expression.toJson(), + 'dart_type': instance.dartType.toJson(), + 'sql_type': _$DriftSqlTypeEnumMap[instance.sqlType]!, + 'dart_type_is_nullable': instance.dartTypeIsNullable, + 'sql_type_is_nullable': instance.sqlTypeIsNullable, + 'also_applies_to_json_conversion': instance.alsoAppliesToJsonConversion, + }; + +const _$DriftSqlTypeEnumMap = { + DriftSqlType.bool: 'bool', + DriftSqlType.string: 'string', + DriftSqlType.bigInt: 'bigInt', + DriftSqlType.int: 'int', + DriftSqlType.dateTime: 'dateTime', + DriftSqlType.blob: 'blob', + DriftSqlType.double: 'double', +}; diff --git a/drift_dev/lib/src/generated/analysis/results/dart.g.dart b/drift_dev/lib/src/generated/analysis/results/dart.g.dart new file mode 100644 index 00000000..af150561 --- /dev/null +++ b/drift_dev/lib/src/generated/analysis/results/dart.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../../../analysis/results/dart.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DartTopLevelSymbol _$DartTopLevelSymbolFromJson(Map json) => $checkedCreate( + 'DartTopLevelSymbol', + json, + ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['lexeme', 'element_id'], + ); + final val = DartTopLevelSymbol( + $checkedConvert('lexeme', (v) => v as String), + $checkedConvert( + 'element_id', (v) => DriftElementId.fromJson(v as Map)), + ); + return val; + }, + fieldKeyMap: const {'elementId': 'element_id'}, + ); + +Map _$DartTopLevelSymbolToJson(DartTopLevelSymbol instance) => + { + 'lexeme': instance.lexeme, + 'element_id': instance.elementId.toJson(), + }; diff --git a/drift_dev/lib/src/generated/analysis/results/element.g.dart b/drift_dev/lib/src/generated/analysis/results/element.g.dart new file mode 100644 index 00000000..ec0cb127 --- /dev/null +++ b/drift_dev/lib/src/generated/analysis/results/element.g.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../../../analysis/results/element.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DriftElementId _$DriftElementIdFromJson(Map json) => $checkedCreate( + 'DriftElementId', + json, + ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['library_uri', 'name'], + ); + final val = DriftElementId( + $checkedConvert('library_uri', (v) => Uri.parse(v as String)), + $checkedConvert('name', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const {'libraryUri': 'library_uri'}, + ); + +Map _$DriftElementIdToJson(DriftElementId instance) => + { + 'library_uri': instance.libraryUri.toString(), + 'name': instance.name, + }; + +DriftDeclaration _$DriftDeclarationFromJson(Map json) => $checkedCreate( + 'DriftDeclaration', + json, + ($checkedConvert) { + $checkKeys( + json, + allowedKeys: const ['source_uri', 'offset'], + ); + final val = DriftDeclaration( + $checkedConvert('source_uri', (v) => Uri.parse(v as String)), + $checkedConvert('offset', (v) => v as int), + ); + return val; + }, + fieldKeyMap: const {'sourceUri': 'source_uri'}, + ); + +Map _$DriftDeclarationToJson(DriftDeclaration instance) => + { + 'source_uri': instance.sourceUri.toString(), + 'offset': instance.offset, + }; diff --git a/drift_dev/lib/src/analyzer/options.g.dart b/drift_dev/lib/src/generated/analyzer/options.g.dart similarity index 99% rename from drift_dev/lib/src/analyzer/options.g.dart rename to drift_dev/lib/src/generated/analyzer/options.g.dart index e0af0af8..9951a17a 100644 --- a/drift_dev/lib/src/analyzer/options.g.dart +++ b/drift_dev/lib/src/generated/analyzer/options.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'options.dart'; +part of '../../analyzer/options.dart'; // ************************************************************************** // JsonSerializableGenerator diff --git a/drift_dev/pubspec.yaml b/drift_dev/pubspec.yaml index a7c8c27d..566effa6 100644 --- a/drift_dev/pubspec.yaml +++ b/drift_dev/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: recase: '>=2.0.1 <5.0.0' meta: ^1.1.0 path: ^1.6.0 - json_annotation: ^4.5.0 + json_annotation: ^4.6.0 stream_transform: '>=0.1.0 <3.0.0' # CLI diff --git a/drift_dev/test/analysis/resolver/drift/table_test.dart b/drift_dev/test/analysis/resolver/drift/table_test.dart new file mode 100644 index 00000000..91eeaa1f --- /dev/null +++ b/drift_dev/test/analysis/resolver/drift/table_test.dart @@ -0,0 +1,56 @@ +import 'package:drift/drift.dart' show DriftSqlType; +import 'package:drift_dev/src/analysis/results/column.dart'; +import 'package:drift_dev/src/analysis/results/table.dart'; +import 'package:test/test.dart'; + +import '../../test_utils.dart'; + +void main() { + test('reports foreign keys in drift model', () async { + final backend = TestBackend.inTest({ + 'a|lib/a.drift': ''' +CREATE TABLE a ( + foo INTEGER PRIMARY KEY, + bar INTEGER REFERENCES b (bar) +); + +CREATE TABLE b ( + bar INTEGER NOT NULL +); +''', + }); + + final state = + await backend.driver.fullyAnalyze(Uri.parse('package:a/a.drift')); + + expect(state, hasNoErrors); + final results = state.analysis.values.toList(); + + final a = results[0].result! as DriftTable; + final aFoo = a.columns[0]; + final aBar = a.columns[1]; + + final b = results[1].result! as DriftTable; + final bBar = b.columns[0]; + + expect(aFoo.sqlType, DriftSqlType.int); + expect(aFoo.nullable, isFalse); + expect(aFoo.constraints, isEmpty); + expect(aFoo.customConstraints, isNull); + + expect(aBar.sqlType, DriftSqlType.int); + expect(aBar.nullable, isTrue); + expect(aBar.constraints, [ + isA() + .having((e) => e.otherColumn, 'otherColumn', bBar) + .having((e) => e.onUpdate, 'onUpdate', isNull) + .having((e) => e.onDelete, 'onDelete', isNull) + ]); + expect(aBar.customConstraints, isNull); + + expect(bBar.sqlType, DriftSqlType.int); + expect(bBar.nullable, isFalse); + expect(bBar.constraints, isEmpty); + expect(bBar.customConstraints, isNull); + }); +}