mirror of https://github.com/AMT-Cheif/drift.git
Fix null analysis for Dart views (#2031)
This commit is contained in:
parent
16f586f444
commit
1681c83bea
|
@ -465,15 +465,14 @@ class $TodoItemsTable extends TodoItems
|
||||||
|
|
||||||
class TodoCategoryItemCountData extends DataClass {
|
class TodoCategoryItemCountData extends DataClass {
|
||||||
final String name;
|
final String name;
|
||||||
final int itemCount;
|
final int? itemCount;
|
||||||
const TodoCategoryItemCountData(
|
const TodoCategoryItemCountData({required this.name, this.itemCount});
|
||||||
{required this.name, required this.itemCount});
|
|
||||||
factory TodoCategoryItemCountData.fromJson(Map<String, dynamic> json,
|
factory TodoCategoryItemCountData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return TodoCategoryItemCountData(
|
return TodoCategoryItemCountData(
|
||||||
name: serializer.fromJson<String>(json['name']),
|
name: serializer.fromJson<String>(json['name']),
|
||||||
itemCount: serializer.fromJson<int>(json['itemCount']),
|
itemCount: serializer.fromJson<int?>(json['itemCount']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
factory TodoCategoryItemCountData.fromJsonString(String encodedJson,
|
factory TodoCategoryItemCountData.fromJsonString(String encodedJson,
|
||||||
|
@ -486,14 +485,15 @@ class TodoCategoryItemCountData extends DataClass {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'name': serializer.toJson<String>(name),
|
'name': serializer.toJson<String>(name),
|
||||||
'itemCount': serializer.toJson<int>(itemCount),
|
'itemCount': serializer.toJson<int?>(itemCount),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TodoCategoryItemCountData copyWith({String? name, int? itemCount}) =>
|
TodoCategoryItemCountData copyWith(
|
||||||
|
{String? name, Value<int?> itemCount = const Value.absent()}) =>
|
||||||
TodoCategoryItemCountData(
|
TodoCategoryItemCountData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
itemCount: itemCount ?? this.itemCount,
|
itemCount: itemCount.present ? itemCount.value : this.itemCount,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -525,7 +525,7 @@ class $TodoCategoryItemCountView
|
||||||
$TodoCategoriesTable get todoCategories =>
|
$TodoCategoriesTable get todoCategories =>
|
||||||
attachedDatabase.todoCategories.createAlias('t1');
|
attachedDatabase.todoCategories.createAlias('t1');
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [todoCategories.name, itemCount];
|
List<GeneratedColumn> get $columns => [name, itemCount];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? entityName;
|
String get aliasedName => _alias ?? entityName;
|
||||||
@override
|
@override
|
||||||
|
@ -542,15 +542,16 @@ class $TodoCategoryItemCountView
|
||||||
name: attachedDatabase.options.types
|
name: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}name'])!,
|
||||||
itemCount: attachedDatabase.options.types
|
itemCount: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}item_count'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}item_count']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
late final GeneratedColumn<String> name = GeneratedColumn<String>(
|
||||||
'name', aliasedName, false,
|
'name', aliasedName, false,
|
||||||
type: DriftSqlType.string);
|
type: DriftSqlType.string,
|
||||||
|
generatedAs: GeneratedAs(todoCategories.name, false));
|
||||||
late final GeneratedColumn<int> itemCount = GeneratedColumn<int>(
|
late final GeneratedColumn<int> itemCount = GeneratedColumn<int>(
|
||||||
'item_count', aliasedName, false,
|
'item_count', aliasedName, true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int,
|
||||||
generatedAs: GeneratedAs(todoItems.id.count(), false));
|
generatedAs: GeneratedAs(todoItems.id.count(), false));
|
||||||
@override
|
@override
|
||||||
|
@ -569,15 +570,14 @@ class $TodoCategoryItemCountView
|
||||||
|
|
||||||
class TodoItemWithCategoryNameViewData extends DataClass {
|
class TodoItemWithCategoryNameViewData extends DataClass {
|
||||||
final int id;
|
final int id;
|
||||||
final String title;
|
final String? title;
|
||||||
const TodoItemWithCategoryNameViewData(
|
const TodoItemWithCategoryNameViewData({required this.id, this.title});
|
||||||
{required this.id, required this.title});
|
|
||||||
factory TodoItemWithCategoryNameViewData.fromJson(Map<String, dynamic> json,
|
factory TodoItemWithCategoryNameViewData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return TodoItemWithCategoryNameViewData(
|
return TodoItemWithCategoryNameViewData(
|
||||||
id: serializer.fromJson<int>(json['id']),
|
id: serializer.fromJson<int>(json['id']),
|
||||||
title: serializer.fromJson<String>(json['title']),
|
title: serializer.fromJson<String?>(json['title']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
factory TodoItemWithCategoryNameViewData.fromJsonString(String encodedJson,
|
factory TodoItemWithCategoryNameViewData.fromJsonString(String encodedJson,
|
||||||
|
@ -590,14 +590,15 @@ class TodoItemWithCategoryNameViewData extends DataClass {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'id': serializer.toJson<int>(id),
|
'id': serializer.toJson<int>(id),
|
||||||
'title': serializer.toJson<String>(title),
|
'title': serializer.toJson<String?>(title),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TodoItemWithCategoryNameViewData copyWith({int? id, String? title}) =>
|
TodoItemWithCategoryNameViewData copyWith(
|
||||||
|
{int? id, Value<String?> title = const Value.absent()}) =>
|
||||||
TodoItemWithCategoryNameViewData(
|
TodoItemWithCategoryNameViewData(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
title: title ?? this.title,
|
title: title.present ? title.value : this.title,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -629,7 +630,7 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
|
||||||
$TodoCategoriesTable get todoCategories =>
|
$TodoCategoriesTable get todoCategories =>
|
||||||
attachedDatabase.todoCategories.createAlias('t1');
|
attachedDatabase.todoCategories.createAlias('t1');
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [todoItems.id, title];
|
List<GeneratedColumn> get $columns => [id, title];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? entityName;
|
String get aliasedName => _alias ?? entityName;
|
||||||
@override
|
@override
|
||||||
|
@ -646,14 +647,15 @@ class $TodoItemWithCategoryNameViewView extends ViewInfo<
|
||||||
id: attachedDatabase.options.types
|
id: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
|
||||||
title: attachedDatabase.options.types
|
title: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}title'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}title']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final GeneratedColumn<int> id =
|
late final GeneratedColumn<int> id = GeneratedColumn<int>(
|
||||||
GeneratedColumn<int>('id', aliasedName, false, type: DriftSqlType.int);
|
'id', aliasedName, false,
|
||||||
|
type: DriftSqlType.int, generatedAs: GeneratedAs(todoItems.id, false));
|
||||||
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
||||||
'title', aliasedName, false,
|
'title', aliasedName, true,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
generatedAs: GeneratedAs(
|
generatedAs: GeneratedAs(
|
||||||
todoItems.title +
|
todoItems.title +
|
||||||
|
|
|
@ -83,8 +83,8 @@ void main() {
|
||||||
verify(mockExecutor.runCustom(
|
verify(mockExecutor.runCustom(
|
||||||
'CREATE VIEW IF NOT EXISTS todo_with_category_view '
|
'CREATE VIEW IF NOT EXISTS todo_with_category_view '
|
||||||
'(title, "desc") AS SELECT '
|
'(title, "desc") AS SELECT '
|
||||||
't0.title AS "t0.title", '
|
't0.title AS "title", '
|
||||||
't1."desc" AS "t1.desc" '
|
't1."desc" AS "desc" '
|
||||||
'FROM todos t0 '
|
'FROM todos t0 '
|
||||||
'INNER JOIN categories t1 '
|
'INNER JOIN categories t1 '
|
||||||
'ON t1.id = t0.category',
|
'ON t1.id = t0.category',
|
||||||
|
|
|
@ -1380,16 +1380,15 @@ class $PureDefaultsTable extends PureDefaults
|
||||||
}
|
}
|
||||||
|
|
||||||
class CategoryTodoCountViewData extends DataClass {
|
class CategoryTodoCountViewData extends DataClass {
|
||||||
final String description;
|
final String? description;
|
||||||
final int itemCount;
|
final int? itemCount;
|
||||||
const CategoryTodoCountViewData(
|
const CategoryTodoCountViewData({this.description, this.itemCount});
|
||||||
{required this.description, required this.itemCount});
|
|
||||||
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
|
factory CategoryTodoCountViewData.fromJson(Map<String, dynamic> json,
|
||||||
{ValueSerializer? serializer}) {
|
{ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return CategoryTodoCountViewData(
|
return CategoryTodoCountViewData(
|
||||||
description: serializer.fromJson<String>(json['description']),
|
description: serializer.fromJson<String?>(json['description']),
|
||||||
itemCount: serializer.fromJson<int>(json['itemCount']),
|
itemCount: serializer.fromJson<int?>(json['itemCount']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
factory CategoryTodoCountViewData.fromJsonString(String encodedJson,
|
factory CategoryTodoCountViewData.fromJsonString(String encodedJson,
|
||||||
|
@ -1401,15 +1400,17 @@ class CategoryTodoCountViewData extends DataClass {
|
||||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'description': serializer.toJson<String>(description),
|
'description': serializer.toJson<String?>(description),
|
||||||
'itemCount': serializer.toJson<int>(itemCount),
|
'itemCount': serializer.toJson<int?>(itemCount),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
CategoryTodoCountViewData copyWith({String? description, int? itemCount}) =>
|
CategoryTodoCountViewData copyWith(
|
||||||
|
{Value<String?> description = const Value.absent(),
|
||||||
|
Value<int?> itemCount = const Value.absent()}) =>
|
||||||
CategoryTodoCountViewData(
|
CategoryTodoCountViewData(
|
||||||
description: description ?? this.description,
|
description: description.present ? description.value : this.description,
|
||||||
itemCount: itemCount ?? this.itemCount,
|
itemCount: itemCount.present ? itemCount.value : this.itemCount,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -1456,19 +1457,19 @@ class $CategoryTodoCountViewView
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
return CategoryTodoCountViewData(
|
return CategoryTodoCountViewData(
|
||||||
description: attachedDatabase.options.types
|
description: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}description'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}description']),
|
||||||
itemCount: attachedDatabase.options.types
|
itemCount: attachedDatabase.options.types
|
||||||
.read(DriftSqlType.int, data['${effectivePrefix}item_count'])!,
|
.read(DriftSqlType.int, data['${effectivePrefix}item_count']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
||||||
'description', aliasedName, false,
|
'description', aliasedName, true,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
generatedAs:
|
generatedAs:
|
||||||
GeneratedAs(categories.description + const Variable('!'), false));
|
GeneratedAs(categories.description + const Variable('!'), false));
|
||||||
late final GeneratedColumn<int> itemCount = GeneratedColumn<int>(
|
late final GeneratedColumn<int> itemCount = GeneratedColumn<int>(
|
||||||
'item_count', aliasedName, false,
|
'item_count', aliasedName, true,
|
||||||
type: DriftSqlType.int,
|
type: DriftSqlType.int,
|
||||||
generatedAs: GeneratedAs(todos.id.count(), false));
|
generatedAs: GeneratedAs(todos.id.count(), false));
|
||||||
@override
|
@override
|
||||||
|
@ -1547,7 +1548,7 @@ class $TodoWithCategoryViewView
|
||||||
$CategoriesTable get categories =>
|
$CategoriesTable get categories =>
|
||||||
attachedDatabase.categories.createAlias('t1');
|
attachedDatabase.categories.createAlias('t1');
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [todos.title, categories.description];
|
List<GeneratedColumn> get $columns => [title, description];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? entityName;
|
String get aliasedName => _alias ?? entityName;
|
||||||
@override
|
@override
|
||||||
|
@ -1569,11 +1570,12 @@ class $TodoWithCategoryViewView
|
||||||
}
|
}
|
||||||
|
|
||||||
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
late final GeneratedColumn<String> title = GeneratedColumn<String>(
|
||||||
'title', aliasedName, false,
|
'title', aliasedName, true,
|
||||||
type: DriftSqlType.string);
|
type: DriftSqlType.string, generatedAs: GeneratedAs(todos.title, false));
|
||||||
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
late final GeneratedColumn<String> description = GeneratedColumn<String>(
|
||||||
'desc', aliasedName, false,
|
'desc', aliasedName, false,
|
||||||
type: DriftSqlType.string);
|
type: DriftSqlType.string,
|
||||||
|
generatedAs: GeneratedAs(categories.description, false));
|
||||||
@override
|
@override
|
||||||
$TodoWithCategoryViewView createAlias(String alias) {
|
$TodoWithCategoryViewView createAlias(String alias) {
|
||||||
return $TodoWithCategoryViewView(attachedDatabase, alias);
|
return $TodoWithCategoryViewView(attachedDatabase, alias);
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
## 2.2.0-dev
|
||||||
|
|
||||||
|
- Fix the nullability of columns generated for Dart-defined views.
|
||||||
|
|
||||||
## 2.1.0
|
## 2.1.0
|
||||||
|
|
||||||
- Analysis support `fts5` tables with external content tables.
|
- Analysis support `fts5` tables with external content tables.
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'package:analyzer/dart/ast/ast.dart';
|
||||||
import 'package:analyzer/dart/ast/visitor.dart';
|
import 'package:analyzer/dart/ast/visitor.dart';
|
||||||
import 'package:analyzer/dart/constant/value.dart';
|
import 'package:analyzer/dart/constant/value.dart';
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
|
||||||
import 'package:analyzer/dart/element/type.dart';
|
import 'package:analyzer/dart/element/type.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift_dev/moor_generator.dart';
|
import 'package:drift_dev/moor_generator.dart';
|
||||||
|
|
|
@ -9,28 +9,32 @@ class ViewParser {
|
||||||
Future<MoorView?> parseView(
|
Future<MoorView?> parseView(
|
||||||
ClassElement element, List<DriftTable> tables) async {
|
ClassElement element, List<DriftTable> tables) async {
|
||||||
final name = await _parseViewName(element);
|
final name = await _parseViewName(element);
|
||||||
final columns = (await _parseColumns(element)).toList();
|
|
||||||
final staticReferences = await _parseStaticReferences(element, tables);
|
|
||||||
final dataClassInfo = _readDataClassInformation(columns, element);
|
|
||||||
final query = await _parseQuery(element, staticReferences, columns);
|
|
||||||
|
|
||||||
final view = MoorView(
|
final staticReferences = await _parseStaticReferences(element, tables);
|
||||||
declaration:
|
final structure = await _parseSelectStructure(element, staticReferences);
|
||||||
DartViewDeclaration(element, base.step.file, staticReferences),
|
final columns =
|
||||||
|
(await _parseColumns(element, structure, staticReferences)).toList();
|
||||||
|
|
||||||
|
final dataClassInfo = _readDataClassInformation(columns, element);
|
||||||
|
|
||||||
|
return MoorView(
|
||||||
|
declaration: DartViewDeclaration(
|
||||||
|
element,
|
||||||
|
base.step.file,
|
||||||
|
structure.primarySource,
|
||||||
|
staticReferences,
|
||||||
|
structure.dartQuerySource,
|
||||||
|
),
|
||||||
name: name,
|
name: name,
|
||||||
dartTypeName: dataClassInfo.enforcedName,
|
dartTypeName: dataClassInfo.enforcedName,
|
||||||
existingRowClass: dataClassInfo.existingClass,
|
existingRowClass: dataClassInfo.existingClass,
|
||||||
customParentClass: dataClassInfo.extending,
|
customParentClass: dataClassInfo.extending,
|
||||||
entityInfoName: '\$${element.name}View',
|
entityInfoName: '\$${element.name}View',
|
||||||
viewQuery: query,
|
)
|
||||||
);
|
..columns = columns
|
||||||
|
..references = [
|
||||||
view.references = [
|
for (final staticRef in staticReferences) staticRef.table,
|
||||||
for (final staticRef in staticReferences) staticRef.table,
|
];
|
||||||
];
|
|
||||||
|
|
||||||
view.columns = columns;
|
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_DataClassInformation _readDataClassInformation(
|
_DataClassInformation _readDataClassInformation(
|
||||||
|
@ -114,56 +118,89 @@ class ViewParser {
|
||||||
return ReCase(element.name).snakeCase;
|
return ReCase(element.name).snakeCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Iterable<DriftColumn>> _parseColumns(ClassElement element) async {
|
Future<List<DriftColumn>> _parseColumns(
|
||||||
final columnNames = element.allSupertypes
|
ClassElement element,
|
||||||
.map((t) => t.element2)
|
_ParsedDartViewSelect structure,
|
||||||
.followedBy([element])
|
List<TableReferenceInDartView> references,
|
||||||
.expand((e) => e.fields)
|
) async {
|
||||||
.where((field) =>
|
final columns = <DriftColumn>[];
|
||||||
(isExpression(field.type) || isColumn(field.type)) &&
|
|
||||||
field.getter != null &&
|
|
||||||
!field.getter!.isSynthetic)
|
|
||||||
.map((field) => field.name)
|
|
||||||
.toSet();
|
|
||||||
|
|
||||||
final fields = columnNames.map((name) {
|
for (final columnReference in structure.selectedColumns) {
|
||||||
final getter = element.getGetter(name) ??
|
final parts = columnReference.toSource().split('.');
|
||||||
element.lookUpInheritedConcreteGetter(name, element.library);
|
|
||||||
return getter!.variable;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final results = await Future.wait(fields.map((field) async {
|
// Column reference like `foo.bar`, where `foo` is a table that has been
|
||||||
final dartType = (field.type as InterfaceType).typeArguments[0];
|
// referenced in this view.
|
||||||
final typeName = dartType.nameIfInterfaceType!;
|
if (parts.length > 1) {
|
||||||
final sqlType = _dartTypeToColumnType(typeName);
|
final reference =
|
||||||
|
references.firstWhereOrNull((ref) => ref.name == parts[0]);
|
||||||
if (sqlType == null) {
|
if (reference == null) {
|
||||||
final String errorMessage;
|
base.step.reportError(ErrorInDartCode(
|
||||||
if (typeName == 'dynamic') {
|
message: 'Table named `${parts[0]}` not found! Maybe not '
|
||||||
errorMessage = 'You must specify Expression<> type argument';
|
'included in @DriftDatabase or not belongs to this database',
|
||||||
} else {
|
affectedElement: element,
|
||||||
errorMessage =
|
affectedNode: columnReference,
|
||||||
'Invalid Expression<> type argument `$typeName` found. '
|
));
|
||||||
'Must be one of: '
|
continue;
|
||||||
'bool, String, int, DateTime, Uint8List, double';
|
|
||||||
}
|
}
|
||||||
throw analysisError(base.step, field, errorMessage);
|
|
||||||
|
final column = reference.table.columns
|
||||||
|
.firstWhere((col) => col.dartGetterName == parts[1]);
|
||||||
|
column.table = reference.table;
|
||||||
|
|
||||||
|
columns.add(DriftColumn(
|
||||||
|
type: column.type,
|
||||||
|
nullable: column.nullable || structure.referenceIsNullable(reference),
|
||||||
|
dartGetterName: column.dartGetterName,
|
||||||
|
name: column.name,
|
||||||
|
generatedAs: ColumnGeneratedAs(
|
||||||
|
'${reference.name}.${column.dartGetterName}', false),
|
||||||
|
typeConverter: column.typeConverter,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
// Locally-defined column, defined as a getter on this view class.
|
||||||
|
final getter = element.thisType.getGetter(parts[0]);
|
||||||
|
|
||||||
|
if (getter == null) {
|
||||||
|
base.step.reportError(ErrorInDartCode(
|
||||||
|
message: 'This column could not be found in the local view.',
|
||||||
|
affectedElement: element,
|
||||||
|
affectedNode: columnReference,
|
||||||
|
));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final dartType = (getter.returnType as InterfaceType).typeArguments[0];
|
||||||
|
final typeName = dartType.nameIfInterfaceType!;
|
||||||
|
final sqlType = _dartTypeToColumnType(typeName);
|
||||||
|
|
||||||
|
if (sqlType == null) {
|
||||||
|
final String errorMessage;
|
||||||
|
if (typeName == 'dynamic') {
|
||||||
|
errorMessage = 'You must specify Expression<> type argument';
|
||||||
|
} else {
|
||||||
|
errorMessage =
|
||||||
|
'Invalid Expression<> type argument `$typeName` found. '
|
||||||
|
'Must be one of: '
|
||||||
|
'bool, String, int, DateTime, Uint8List, double';
|
||||||
|
}
|
||||||
|
throw analysisError(base.step, getter, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
final node =
|
||||||
|
await base.loadElementDeclaration(getter) as MethodDeclaration;
|
||||||
|
final expression = (node.body as ExpressionFunctionBody).expression;
|
||||||
|
|
||||||
|
columns.add(DriftColumn(
|
||||||
|
type: sqlType,
|
||||||
|
dartGetterName: getter.name,
|
||||||
|
name: ColumnName.implicitly(ReCase(getter.name).snakeCase),
|
||||||
|
nullable: true,
|
||||||
|
generatedAs: ColumnGeneratedAs(expression.toString(), false),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final node =
|
return columns;
|
||||||
await base.loadElementDeclaration(field.getter!) as MethodDeclaration;
|
|
||||||
final expression = (node.body as ExpressionFunctionBody).expression;
|
|
||||||
|
|
||||||
return DriftColumn(
|
|
||||||
type: sqlType,
|
|
||||||
dartGetterName: field.name,
|
|
||||||
name: ColumnName.implicitly(ReCase(field.name).snakeCase),
|
|
||||||
nullable: dartType.nullabilitySuffix == NullabilitySuffix.question,
|
|
||||||
generatedAs: ColumnGeneratedAs(expression.toString(), false),
|
|
||||||
);
|
|
||||||
}).toList());
|
|
||||||
|
|
||||||
return results.whereType();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DriftSqlType? _dartTypeToColumnType(String name) {
|
DriftSqlType? _dartTypeToColumnType(String name) {
|
||||||
|
@ -207,98 +244,124 @@ class ViewParser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ViewQueryInformation> _parseQuery(
|
Future<_ParsedDartViewSelect> _parseSelectStructure(
|
||||||
ClassElement element,
|
ClassElement element,
|
||||||
List<TableReferenceInDartView> references,
|
List<TableReferenceInDartView> references,
|
||||||
List<DriftColumn> columns) async {
|
) async {
|
||||||
final as =
|
final as =
|
||||||
element.methods.where((method) => method.name == 'as').firstOrNull;
|
element.methods.where((method) => method.name == 'as').firstOrNull;
|
||||||
|
|
||||||
if (as != null) {
|
if (as == null) {
|
||||||
try {
|
throw analysisError(
|
||||||
final node = await base.loadElementDeclaration(as);
|
base.step, element, 'Missing `as()` query declaration');
|
||||||
|
}
|
||||||
|
|
||||||
final body = (node as MethodDeclaration).body;
|
final node = await base.loadElementDeclaration(as);
|
||||||
if (body is! ExpressionFunctionBody) {
|
final body = (node as MethodDeclaration).body;
|
||||||
throw analysisError(
|
if (body is! ExpressionFunctionBody) {
|
||||||
|
throw analysisError(
|
||||||
|
base.step,
|
||||||
|
element,
|
||||||
|
'The `as()` query declaration must be an expression (=>). '
|
||||||
|
'Block function body `{ return x; }` not acceptable.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final innerJoins = <TableReferenceInDartView>[];
|
||||||
|
final outerJoins = <TableReferenceInDartView>[];
|
||||||
|
|
||||||
|
// We have something like Query as() => select([...]).from(foo).join(...).
|
||||||
|
// First, crawl up so get the `select`:
|
||||||
|
Expression? target = body.expression;
|
||||||
|
for (;;) {
|
||||||
|
if (target is MethodInvocation) {
|
||||||
|
if (target.target == null) break;
|
||||||
|
|
||||||
|
final name = target.methodName.toSource();
|
||||||
|
if (name == 'join') {
|
||||||
|
final joinList = target.argumentList.arguments[0] as ListLiteral;
|
||||||
|
for (final entry in joinList.elements) {
|
||||||
|
// Do we have something like innerJoin(foo, bar)?
|
||||||
|
if (entry is MethodInvocation) {
|
||||||
|
final isInnerJoin = entry.methodName.toSource() == 'innerJoin';
|
||||||
|
final table = references.firstWhereOrNull((element) =>
|
||||||
|
element.name == entry.argumentList.arguments[0].toSource());
|
||||||
|
|
||||||
|
if (table != null) {
|
||||||
|
final list = isInnerJoin ? innerJoins : outerJoins;
|
||||||
|
list.add(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target = target.target;
|
||||||
|
} else if (target is CascadeExpression) {
|
||||||
|
target = target.target;
|
||||||
|
} else {
|
||||||
|
throw analysisError(
|
||||||
base.step,
|
base.step,
|
||||||
element,
|
element,
|
||||||
'The `as()` query declaration must be an expression (=>). '
|
'The `as()` query declaration contains invalid expression type '
|
||||||
'Block function body `{ return x; }` not acceptable.',
|
'${target.runtimeType}');
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression? target = body.expression;
|
|
||||||
for (;;) {
|
|
||||||
if (target is MethodInvocation) {
|
|
||||||
if (target.target == null) break;
|
|
||||||
target = target.target;
|
|
||||||
} else if (target is CascadeExpression) {
|
|
||||||
target = target.target;
|
|
||||||
} else {
|
|
||||||
throw analysisError(
|
|
||||||
base.step,
|
|
||||||
element,
|
|
||||||
'The `as()` query declaration contains invalid expression type '
|
|
||||||
'${target.runtimeType}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.methodName.toString() != 'select') {
|
|
||||||
throw analysisError(
|
|
||||||
base.step,
|
|
||||||
element,
|
|
||||||
'The `as()` query declaration must be started '
|
|
||||||
'with `select(columns).from(table)');
|
|
||||||
}
|
|
||||||
|
|
||||||
final columnListLiteral =
|
|
||||||
target.argumentList.arguments[0] as ListLiteral;
|
|
||||||
final columnList =
|
|
||||||
columnListLiteral.elements.map((col) => col.toString()).map((col) {
|
|
||||||
final parts = col.split('.');
|
|
||||||
if (parts.length > 1) {
|
|
||||||
final reference =
|
|
||||||
references.firstWhereOrNull((ref) => ref.name == parts[0]);
|
|
||||||
if (reference == null) {
|
|
||||||
throw analysisError(
|
|
||||||
base.step,
|
|
||||||
element,
|
|
||||||
'Table named `${parts[0]}` not found! Maybe not included in '
|
|
||||||
'@DriftDatabase or not belongs to this database');
|
|
||||||
}
|
|
||||||
final column = reference.table.columns
|
|
||||||
.firstWhere((col) => col.dartGetterName == parts[1]);
|
|
||||||
column.table = reference.table;
|
|
||||||
return MapEntry(
|
|
||||||
'${reference.name}.${column.dartGetterName}', column);
|
|
||||||
}
|
|
||||||
final column =
|
|
||||||
columns.firstWhere((col) => col.dartGetterName == parts[0]);
|
|
||||||
return MapEntry(column.dartGetterName, column);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
target = target.parent as MethodInvocation;
|
|
||||||
if (target.methodName.toString() != 'from') {
|
|
||||||
throw analysisError(
|
|
||||||
base.step,
|
|
||||||
element,
|
|
||||||
'The `as()` query declaration must be started '
|
|
||||||
'with `select(columns).from(table)');
|
|
||||||
}
|
|
||||||
|
|
||||||
final from = target.argumentList.arguments[0].toString();
|
|
||||||
final query =
|
|
||||||
body.expression.toString().substring(target.toString().length);
|
|
||||||
|
|
||||||
return ViewQueryInformation(columnList, from, query);
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
throw analysisError(
|
|
||||||
base.step, element, 'Failed to parse view `as()` query');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw analysisError(base.step, element, 'Missing `as()` query declaration');
|
if (target.methodName.toString() != 'select') {
|
||||||
|
throw analysisError(
|
||||||
|
base.step,
|
||||||
|
element,
|
||||||
|
'The `as()` query declaration must be started '
|
||||||
|
'with `select(columns).from(table)');
|
||||||
|
}
|
||||||
|
|
||||||
|
final columnListLiteral = target.argumentList.arguments[0] as ListLiteral;
|
||||||
|
final columnExpressions =
|
||||||
|
columnListLiteral.elements.whereType<Expression>().toList();
|
||||||
|
|
||||||
|
target = target.parent as MethodInvocation;
|
||||||
|
if (target.methodName.toString() != 'from') {
|
||||||
|
throw analysisError(
|
||||||
|
base.step,
|
||||||
|
element,
|
||||||
|
'The `as()` query declaration must be started '
|
||||||
|
'with `select(columns).from(table)');
|
||||||
|
}
|
||||||
|
|
||||||
|
final from = target.argumentList.arguments[0].toSource();
|
||||||
|
final resolvedFrom =
|
||||||
|
references.firstWhereOrNull((element) => element.name == from);
|
||||||
|
if (resolvedFrom == null) {
|
||||||
|
base.step.reportError(
|
||||||
|
ErrorInDartCode(
|
||||||
|
message: 'Table reference `$from` not found, is it added to this '
|
||||||
|
'view as a getter?',
|
||||||
|
affectedElement: as,
|
||||||
|
affectedNode: target.argumentList,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final query =
|
||||||
|
body.expression.toString().substring(target.toString().length);
|
||||||
|
|
||||||
|
return _ParsedDartViewSelect(
|
||||||
|
resolvedFrom, innerJoins, outerJoins, columnExpressions, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ParsedDartViewSelect {
|
||||||
|
final TableReferenceInDartView? primarySource;
|
||||||
|
final List<TableReferenceInDartView> innerJoins;
|
||||||
|
final List<TableReferenceInDartView> outerJoins;
|
||||||
|
|
||||||
|
final List<Expression> selectedColumns;
|
||||||
|
final String dartQuerySource;
|
||||||
|
|
||||||
|
_ParsedDartViewSelect(this.primarySource, this.innerJoins, this.outerJoins,
|
||||||
|
this.selectedColumns, this.dartQuerySource);
|
||||||
|
|
||||||
|
bool referenceIsNullable(TableReferenceInDartView ref) {
|
||||||
|
return ref != primarySource && !innerJoins.contains(ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,17 @@ class DartViewDeclaration implements ViewDeclaration, DartDeclaration {
|
||||||
@override
|
@override
|
||||||
final ClassElement element;
|
final ClassElement element;
|
||||||
|
|
||||||
|
final String dartQuerySource;
|
||||||
|
final TableReferenceInDartView? primaryFrom;
|
||||||
final List<TableReferenceInDartView> staticReferences;
|
final List<TableReferenceInDartView> staticReferences;
|
||||||
|
|
||||||
DartViewDeclaration._(this.declaration, this.element, this.staticReferences);
|
DartViewDeclaration(
|
||||||
|
this.element,
|
||||||
factory DartViewDeclaration(ClassElement element, FoundFile file,
|
FoundFile file,
|
||||||
List<TableReferenceInDartView> staticReferences) {
|
this.primaryFrom,
|
||||||
return DartViewDeclaration._(
|
this.staticReferences,
|
||||||
SourceRange.fromElementAndFile(element, file),
|
this.dartQuerySource,
|
||||||
element,
|
) : declaration = SourceRange.fromElementAndFile(element, file);
|
||||||
staticReferences,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TableReferenceInDartView {
|
class TableReferenceInDartView {
|
||||||
|
|
|
@ -42,8 +42,6 @@ class MoorView extends DriftEntityWithResultSet {
|
||||||
@override
|
@override
|
||||||
final String? customParentClass;
|
final String? customParentClass;
|
||||||
|
|
||||||
final ViewQueryInformation? viewQuery;
|
|
||||||
|
|
||||||
MoorView({
|
MoorView({
|
||||||
this.declaration,
|
this.declaration,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -51,7 +49,6 @@ class MoorView extends DriftEntityWithResultSet {
|
||||||
required this.entityInfoName,
|
required this.entityInfoName,
|
||||||
this.existingRowClass,
|
this.existingRowClass,
|
||||||
this.customParentClass,
|
this.customParentClass,
|
||||||
this.viewQuery,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -102,13 +99,3 @@ class MoorView extends DriftEntityWithResultSet {
|
||||||
@override
|
@override
|
||||||
String get displayName => name;
|
String get displayName => name;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewQueryInformation {
|
|
||||||
/// All columns from this Dart-defined view, in the order in which they were
|
|
||||||
/// added to the `query` getter.
|
|
||||||
final List<MapEntry<String, DriftColumn>> columns;
|
|
||||||
final String from;
|
|
||||||
final String query;
|
|
||||||
|
|
||||||
ViewQueryInformation(this.columns, this.from, this.query);
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import 'package:drift_dev/writer.dart';
|
||||||
class DataClassWriter {
|
class DataClassWriter {
|
||||||
final DriftEntityWithResultSet table;
|
final DriftEntityWithResultSet table;
|
||||||
final Scope scope;
|
final Scope scope;
|
||||||
final columns = <DriftColumn>[];
|
|
||||||
|
List<DriftColumn> get columns => table.columns;
|
||||||
|
|
||||||
bool get isInsertable => table is DriftTable;
|
bool get isInsertable => table is DriftTable;
|
||||||
|
|
||||||
|
@ -30,14 +31,6 @@ class DataClassWriter {
|
||||||
_buffer.writeln('{');
|
_buffer.writeln('{');
|
||||||
}
|
}
|
||||||
|
|
||||||
// write view columns
|
|
||||||
final view = table;
|
|
||||||
if (view is MoorView && view.viewQuery != null) {
|
|
||||||
columns.addAll(view.viewQuery!.columns.map((e) => e.value));
|
|
||||||
} else {
|
|
||||||
columns.addAll(table.columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write individual fields
|
// write individual fields
|
||||||
for (final column in columns) {
|
for (final column in columns) {
|
||||||
if (column.documentationComment != null) {
|
if (column.documentationComment != null) {
|
||||||
|
|
|
@ -189,14 +189,7 @@ abstract class TableOrViewWriter {
|
||||||
writer.writeArguments(buffer);
|
writer.writeArguments(buffer);
|
||||||
buffer.write(';\n');
|
buffer.write(';\n');
|
||||||
} else {
|
} else {
|
||||||
List<DriftColumn> columns;
|
final columns = tableOrView.columns;
|
||||||
|
|
||||||
final view = tableOrView;
|
|
||||||
if (view is MoorView && view.viewQuery != null) {
|
|
||||||
columns = view.viewQuery!.columns.map((e) => e.value).toList();
|
|
||||||
} else {
|
|
||||||
columns = tableOrView.columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
final writer = RowMappingWriter(
|
final writer = RowMappingWriter(
|
||||||
positional: const [],
|
positional: const [],
|
||||||
|
|
|
@ -63,13 +63,7 @@ class ViewWriter extends TableOrViewWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.viewQuery == null) {
|
writeGetColumnsOverride();
|
||||||
writeGetColumnsOverride();
|
|
||||||
} else {
|
|
||||||
final columns = view.viewQuery!.columns.map((e) => e.key).join(', ');
|
|
||||||
buffer.write('@override\nList<GeneratedColumn> get \$columns => '
|
|
||||||
'[$columns];\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
..write('@override\nString get aliasedName => '
|
..write('@override\nString get aliasedName => '
|
||||||
|
@ -87,21 +81,8 @@ class ViewWriter extends TableOrViewWriter {
|
||||||
writeAsDslTable();
|
writeAsDslTable();
|
||||||
writeMappingMethod(scope);
|
writeMappingMethod(scope);
|
||||||
|
|
||||||
final columns = view.viewQuery?.columns.map((e) => e.value) ?? view.columns;
|
for (final column in view.columns) {
|
||||||
for (final column in columns) {
|
writeColumnGetter(column, scope.generationOptions, false);
|
||||||
if (view.columns.contains(column)) {
|
|
||||||
writeColumnGetter(column, scope.generationOptions, false);
|
|
||||||
} else {
|
|
||||||
// This column only exists as a getter so that it can be referenced in
|
|
||||||
// Dart, but it wasn't defined by the user. Instead, the column is
|
|
||||||
// implicitly generated from a entry in the `select()` query clause.
|
|
||||||
// We can drop all information from it since only the name is relevant.
|
|
||||||
final shortColumn = DriftColumn(
|
|
||||||
type: column.type,
|
|
||||||
dartGetterName: column.dartGetterName,
|
|
||||||
name: column.name);
|
|
||||||
writeColumnGetter(shortColumn, scope.generationOptions, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_writeAliasGenerator();
|
_writeAliasGenerator();
|
||||||
|
@ -130,13 +111,16 @@ class ViewWriter extends TableOrViewWriter {
|
||||||
|
|
||||||
void _writeQuery() {
|
void _writeQuery() {
|
||||||
buffer.write('@override\nQuery? get query => ');
|
buffer.write('@override\nQuery? get query => ');
|
||||||
final query = view.viewQuery;
|
|
||||||
if (query != null) {
|
if (view.isDeclaredInDart) {
|
||||||
buffer.write('(attachedDatabase.selectOnly(${query.from})'
|
final definition = view.declaration as DartViewDeclaration;
|
||||||
'..addColumns(\$columns))'
|
|
||||||
'${query.query};');
|
buffer
|
||||||
|
..write('(attachedDatabase.selectOnly(${definition.primaryFrom?.name})'
|
||||||
|
'..addColumns(\$columns))')
|
||||||
|
..writeln('${definition.dartQuerySource};');
|
||||||
} else {
|
} else {
|
||||||
buffer.write('null;\n');
|
buffer.writeln('null;');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue