assumeCorrectReference resovels #2715

assumeCorrectReference option added
assume the correct reference of the dart code in views, when resolver fails.
This commit is contained in:
Heinrich 2023-11-07 13:59:55 +08:00 committed by Simon Binder
parent 39018d381c
commit 7c2c17d5e4
7 changed files with 197 additions and 11 deletions

View File

@ -102,6 +102,9 @@ class DriftOptions {
@JsonKey(name: 'write_to_columns_mixins', defaultValue: false)
final bool writeToColumnsMixins;
@JsonKey(name: 'assume_correct_reference', defaultValue: false)
final bool assumeCorrectReference;
@JsonKey(name: 'has_separate_analyzer', defaultValue: false)
final bool hasDriftAnalyzer;
@ -135,6 +138,7 @@ class DriftOptions {
this.writeToColumnsMixins = false,
this.fatalWarnings = false,
this.hasDriftAnalyzer = false,
this.assumeCorrectReference = false,
});
DriftOptions({
@ -160,6 +164,7 @@ class DriftOptions {
required this.fatalWarnings,
required this.preamble,
required this.hasDriftAnalyzer,
required this.assumeCorrectReference,
this.dialect,
}) {
// ignore: deprecated_member_use_from_same_package

View File

@ -34,6 +34,7 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
structure.dartQuerySource,
structure.primarySource,
staticReferences,
structure.staticSource
),
references: [
for (final reference in staticReferences) reference.table,
@ -178,7 +179,7 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
final from = target.argumentList.arguments[0].toSource();
final resolvedFrom =
references.firstWhereOrNull((element) => element.name == from);
if (resolvedFrom == null) {
if (resolvedFrom == null && !resolver.driver.options.assumeCorrectReference) {
reportError(
DriftAnalysisError.inDartAst(
as,
@ -188,11 +189,28 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
),
);
}
AnnotatedDartCode query;
if (resolvedFrom == null &&
resolver.driver.options.assumeCorrectReference) {
query = AnnotatedDartCode.build(
(builder) => builder.addText(body.expression.toSource().replaceAll(target!.toSource(), '')));
} else {
query = AnnotatedDartCode.build(
(builder) => builder.addAstNode(body.expression, exclude: {target!}));
}
final query = AnnotatedDartCode.build(
(builder) => builder.addAstNode(body.expression, exclude: {target!}));
// if(resolver.driver.options.assumeCorrectReference){
// bool separate = false;
// for (int i = 0; i < query.elements.length; i++) {
// if (separate && query.elements[i] is String && i != query.elements.length-1) {
// query.elements[i]+=',';
// separate = false;
// }
// separate = query.elements[i] is DartTopLevelSymbol;
// }
// }
return _ParsedDartViewSelect(
resolvedFrom, innerJoins, outerJoins, columnExpressions, query);
resolvedFrom, innerJoins, outerJoins, columnExpressions, query, from);
}
Future<List<DriftColumn>> _parseColumns(
@ -209,7 +227,7 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
if (parts.length > 1) {
final reference =
references.firstWhereOrNull((ref) => ref.name == parts[0]);
if (reference == null) {
if (reference == null || reference.table == null) {
reportError(DriftAnalysisError.inDartAst(
discovered.dartElement,
columnReference,
@ -280,7 +298,11 @@ class DartViewResolver extends LocalElementResolver<DiscoveredDartView> {
nameInSql: ReCase(getter.name).snakeCase,
nullable: true,
constraints: [
ColumnGeneratedAs(AnnotatedDartCode.ast(expression), false)
resolver.driver.options.assumeCorrectReference
? ColumnGeneratedAs(AnnotatedDartCode.build((builder) {
builder.addText(expression.toSource());
}), false)
: ColumnGeneratedAs(AnnotatedDartCode.ast(expression), false),
],
));
}
@ -310,8 +332,9 @@ class _ParsedDartViewSelect {
final List<Expression> selectedColumns;
final AnnotatedDartCode dartQuerySource;
final String? staticSource;
_ParsedDartViewSelect(this.primarySource, this.innerJoins, this.outerJoins,
this.selectedColumns, this.dartQuerySource);
this.selectedColumns, this.dartQuerySource, [this.staticSource]);
bool referenceIsNullable(TableReferenceInDartView ref) {
return ref != primarySource && !innerJoins.contains(ref);

View File

@ -96,6 +96,6 @@ class DartViewSource extends DriftViewSource {
final AnnotatedDartCode dartQuerySource;
final TableReferenceInDartView? primaryFrom;
final List<TableReferenceInDartView> staticReferences;
DartViewSource(this.dartQuerySource, this.primaryFrom, this.staticReferences);
final String? staticSource;
DartViewSource(this.dartQuerySource, this.primaryFrom, this.staticReferences, [this.staticSource]);
}

View File

@ -129,7 +129,8 @@ class ElementSerializer {
'staticReferences': [
for (final reference in source.staticReferences)
_serializeTableReferenceInDartView(reference),
]
],
'staticSource':source.staticSource
};
}
@ -657,6 +658,9 @@ class ElementDeserializer {
for (final element in serializedSource.list('staticReferences'))
readReference(element as Map)
],
serializedSource['staticSource'] != null
? serializedSource['staticSource'] as String
: null,
);
} else {
throw UnsupportedError('Unknown view source $serializedSource');

View File

@ -33,6 +33,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
'store_date_time_values_as_text',
'case_from_dart_to_sql',
'write_to_columns_mixins',
'assume_correct_reference',
'has_separate_analyzer',
'preamble',
'fatal_warnings'
@ -94,6 +95,8 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
preamble: $checkedConvert('preamble', (v) => v as String?),
hasDriftAnalyzer: $checkedConvert(
'has_separate_analyzer', (v) => v as bool? ?? false),
assumeCorrectReference: $checkedConvert(
'assume_correct_reference', (v) => v as bool? ?? false),
dialect: $checkedConvert('sql',
(v) => v == null ? null : DialectOptions.fromJson(v as Map)),
);
@ -124,6 +127,7 @@ DriftOptions _$DriftOptionsFromJson(Map json) => $checkedCreate(
'writeToColumnsMixins': 'write_to_columns_mixins',
'fatalWarnings': 'fatal_warnings',
'hasDriftAnalyzer': 'has_separate_analyzer',
'assumeCorrectReference': 'assume_correct_reference',
'dialect': 'sql'
},
);
@ -157,6 +161,7 @@ Map<String, dynamic> _$DriftOptionsToJson(DriftOptions instance) =>
'case_from_dart_to_sql':
_$CaseFromDartToSqlEnumMap[instance.caseFromDartToSql]!,
'write_to_columns_mixins': instance.writeToColumnsMixins,
'assume_correct_reference': instance.assumeCorrectReference,
'has_separate_analyzer': instance.hasDriftAnalyzer,
'preamble': instance.preamble,
'fatal_warnings': instance.fatalWarnings,

View File

@ -138,7 +138,7 @@ class ViewWriter extends TableOrViewWriter {
final source = view.source;
if (source is DartViewSource) {
emitter
..write('(attachedDatabase.selectOnly(${source.primaryFrom?.name})'
..write('(attachedDatabase.selectOnly(${scope.options.assumeCorrectReference? source.primaryFrom?.name ?? source.staticSource:source.primaryFrom?.name})'
'..addColumns(\$columns))')
..writeDart(source.dartQuerySource)
..writeln(';');

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:drift_dev/src/backends/build/analyzer.dart';
@ -614,6 +616,153 @@ class MyDatabase {
expect(readAssets, isEmpty);
});
test('generates views from drift tables', () async {
final debugLogger = Logger('driftBuild');
debugLogger.onRecord.listen((e) => print(e.message));
final result = await emulateDriftBuild(
inputs: {
'a|lib/drift/datastore_db.dart': '''
import 'package:drift/drift.dart';
part 'datastore_db.g.dart';
mixin AutoIncrement on Table {
IntColumn get id => integer().autoIncrement()();
}
@DataClassName('TodoEntry')
class TodosTable extends Table with AutoIncrement {
@override
String get tableName => 'todos';
TextColumn get title => text().withLength(min: 4, max: 16).nullable()();
TextColumn get content => text()();
@JsonKey('target_date')
DateTimeColumn get targetDate => dateTime().nullable().unique()();
IntColumn get category => integer().references(Categories, #id).nullable()();
TextColumn get status => textEnum<TodoStatus>().nullable()();
@override
List<Set<Column>>? get uniqueKeys => [
{title, category},
{title, targetDate},
];
}
enum TodoStatus { open, workInProgress, done }
class Users extends Table with AutoIncrement {
TextColumn get name => text().withLength(min: 6, max: 32).unique()();
BoolColumn get isAwesome => boolean().withDefault(const Constant(true))();
BlobColumn get profilePicture => blob()();
DateTimeColumn get creationTime => dateTime()
// ignore: recursive_getters
.check(creationTime.isBiggerThan(Constant(DateTime.utc(1950))))
.withDefault(currentDateAndTime)();
}
@DataClassName('Category')
class Categories extends Table with AutoIncrement {
TextColumn get description =>
text().named('desc').customConstraint('NOT NULL UNIQUE')();
IntColumn get priority =>
intEnum<CategoryPriority>().withDefault(const Constant(0))();
TextColumn get descriptionInUpperCase =>
text().generatedAs(description.upper())();
}
enum CategoryPriority { low, medium, high }
abstract class CategoryTodoCountView extends View {
TodosTable get todos;
Categories get categories;
Expression<int> get categoryId => categories.id;
Expression<String> get description =>
categories.description + const Variable('!');
Expression<int> get itemCount => todos.id.count();
@override
Query as() => select([categoryId, description, itemCount])
.from(categories)
.join([innerJoin(todos, todos.category.equalsExp(categories.id))])
..groupBy([categories.id]);
}
abstract class ComboGroupView extends View {
late final DatastoreDb attachedDatabase;
IntColumn get comboGroupID => attachedDatabase.comboGroup.comboGroupID;
IntColumn get objectNumber => attachedDatabase.comboGroup.objectNumber;
TextColumn get stringText => attachedDatabase.stringTable.stringText;
// ComboGroup get comboGroup => attachedDatabase.comboGroup;
// late final ComboGroup comboGroup;
@override
Query as() => select([
comboGroupID,
objectNumber,
stringText,
]).from(attachedDatabase.comboGroup).join([
innerJoin(
attachedDatabase.stringTable,
attachedDatabase.stringTable.stringNumberID
.equalsExp(attachedDatabase.comboGroup.nameID)),
]);
}
@DriftDatabase(
tables: [TodosTable, Categories],
include: {'combo_group.drift','string_table.drift'},
views: [CategoryTodoCountView,ComboGroupView],
)
class DatastoreDb extends _\$DatastoreDb {
DatastoreDb(super.e);
@override
int get schemaVersion => 1;
}
''',
'a|lib/drift/combo_group.drift': '''
CREATE TABLE [COMBO_GROUP](
[ComboGroupID] [int] NOT NULL PRIMARY KEY,
[HierStrucID] [bigint] NULL,
[ObjectNumber] [int] NULL,
[NameID] [bigint] NULL,
[OptionBits] [nvarchar](8) NULL,
[SluIndex] [int] NULL,
[HhtSluIndex] [int] NULL);
''',
'a|lib/drift/string_table.drift': '''
CREATE TABLE [STRING_TABLE](
[StringID] [bigint] NOT NULL PRIMARY KEY,
[StringNumberID] [bigint] NULL,
[LangID] [int] NULL,
[IsVisible] [bit] NOT NULL DEFAULT ((1)),
[IsDeleted] [bit] NOT NULL DEFAULT ((0)),
[HierStrucID] [bigint] NULL,
[PosRef] [bigint] NULL,
[StringText] [nvarchar](128) NULL
);
''',
},
modularBuild: true,
options: BuilderOptions({'assume_correct_reference': true}),
logger: debugLogger
);
// var actual = utf8.decode(result.writer.assets[(result.writer.assets.keys
// .firstWhere((key) => key.path == ('lib/drift/datastore_db.drift.dart')))]!);
checkOutputs(
{
'a|lib/drift/datastore_db.drift.dart': decodedMatches(
allOf(
contains(r'attachedDatabase.selectOnly(attachedDatabase.comboGroup)'),
),
),
'a|lib/drift/combo_group.drift.dart' : anything,
'a|lib/drift/string_table.drift.dart' : anything,
},
result.dartOutputs,
result.writer,
);
});
group('reports issues', () {
for (final fatalWarnings in [false, true]) {
group('fatalWarnings: $fatalWarnings', () {