From 8099fee8d7041449c2b13881e1b167f918719f3e Mon Sep 17 00:00:00 2001 From: Marcos Kobuchi Date: Tue, 18 May 2021 21:12:56 -0300 Subject: [PATCH 1/2] Create embed type --- example/lib/database.dart | 5 +- example/lib/database.g.dart | 65 ++++++++--- example/lib/date_time_converter.dart | 9 ++ example/lib/main.dart | 8 +- example/lib/task.dart | 5 +- example/lib/timestamp.dart | 28 +++++ example/pubspec.lock | 31 +++-- example/pubspec.yaml | 2 +- floor_annotation/lib/floor_annotation.dart | 1 + floor_annotation/lib/src/database.dart | 4 + floor_annotation/lib/src/embed.dart | 5 + floor_generator/lib/misc/constants.dart | 1 + .../lib/misc/extension/embeds_extension.dart | 35 ++++++ .../extension/field_element_extension.dart | 14 +++ .../lib/processor/database_processor.dart | 63 ++++++++--- .../lib/processor/embed_processor.dart | 32 ++++++ .../lib/processor/entity_processor.dart | 40 +++++-- .../lib/processor/field_processor.dart | 17 ++- .../lib/processor/queryable_processor.dart | 106 ++++++++++-------- .../lib/processor/view_processor.dart | 4 +- floor_generator/lib/value_object/embed.dart | 25 +++++ floor_generator/lib/value_object/field.dart | 3 + floor_generator/pubspec.lock | 12 +- 23 files changed, 400 insertions(+), 115 deletions(-) create mode 100644 example/lib/date_time_converter.dart create mode 100644 example/lib/timestamp.dart create mode 100644 floor_annotation/lib/src/embed.dart create mode 100644 floor_generator/lib/misc/extension/embeds_extension.dart create mode 100644 floor_generator/lib/misc/extension/field_element_extension.dart create mode 100644 floor_generator/lib/processor/embed_processor.dart create mode 100644 floor_generator/lib/value_object/embed.dart diff --git a/example/lib/database.dart b/example/lib/database.dart index 396a7143..3a4c0638 100644 --- a/example/lib/database.dart +++ b/example/lib/database.dart @@ -1,13 +1,16 @@ import 'dart:async'; +import 'package:example/date_time_converter.dart'; import 'package:example/task.dart'; import 'package:example/task_dao.dart'; +import 'package:example/timestamp.dart'; import 'package:floor/floor.dart'; import 'package:sqflite/sqflite.dart' as sqflite; part 'database.g.dart'; -@Database(version: 1, entities: [Task]) +@TypeConverters([DateTimeConverter]) +@Database(version: 1, entities: [Task], embeds: [Timestamp]) abstract class FlutterDatabase extends FloorDatabase { TaskDao get taskDao; } diff --git a/example/lib/database.g.dart b/example/lib/database.g.dart index 5547d034..de2e142b 100644 --- a/example/lib/database.g.dart +++ b/example/lib/database.g.dart @@ -81,7 +81,7 @@ class _$FlutterDatabase extends FlutterDatabase { }, onCreate: (database, version) async { await database.execute( - 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT NOT NULL)'); + 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT NOT NULL, `timestamp` NOT NULL)'); await callback?.onCreate?.call(database, version); }, @@ -101,22 +101,40 @@ class _$TaskDao extends TaskDao { _taskInsertionAdapter = InsertionAdapter( database, 'Task', - (Task item) => - {'id': item.id, 'message': item.message}, + (Task item) => { + 'id': item.id, + 'message': item.message, + 'timestamp_created_at': + _dateTimeConverter.encode(item.timestamp.createdAt), + 'timestamp_updated_at': + _dateTimeConverter.encode(item.timestamp.updatedAt) + }, changeListener), _taskUpdateAdapter = UpdateAdapter( database, 'Task', ['id'], - (Task item) => - {'id': item.id, 'message': item.message}, + (Task item) => { + 'id': item.id, + 'message': item.message, + 'timestamp_created_at': + _dateTimeConverter.encode(item.timestamp.createdAt), + 'timestamp_updated_at': + _dateTimeConverter.encode(item.timestamp.updatedAt) + }, changeListener), _taskDeletionAdapter = DeletionAdapter( database, 'Task', ['id'], - (Task item) => - {'id': item.id, 'message': item.message}, + (Task item) => { + 'id': item.id, + 'message': item.message, + 'timestamp_created_at': + _dateTimeConverter.encode(item.timestamp.createdAt), + 'timestamp_updated_at': + _dateTimeConverter.encode(item.timestamp.updatedAt) + }, changeListener); final sqflite.DatabaseExecutor database; @@ -134,23 +152,41 @@ class _$TaskDao extends TaskDao { @override Future findTaskById(int id) async { return _queryAdapter.query('SELECT * FROM task WHERE id = ?1', - mapper: (Map row) => - Task(row['id'] as int?, row['message'] as String), + mapper: (Map row) => Task( + row['id'] as int?, + row['message'] as String, + Timestamp( + createdAt: _dateTimeConverter + .decode(row['timestamp_created_at'] as int), + updatedAt: _dateTimeConverter + .decode(row['timestamp_updated_at'] as int))), arguments: [id]); } @override Future> findAllTasks() async { return _queryAdapter.queryList('SELECT * FROM task', - mapper: (Map row) => - Task(row['id'] as int?, row['message'] as String)); + mapper: (Map row) => Task( + row['id'] as int?, + row['message'] as String, + Timestamp( + createdAt: _dateTimeConverter + .decode(row['timestamp_created_at'] as int), + updatedAt: _dateTimeConverter + .decode(row['timestamp_updated_at'] as int)))); } @override Stream> findAllTasksAsStream() { return _queryAdapter.queryListStream('SELECT * FROM task', - mapper: (Map row) => - Task(row['id'] as int?, row['message'] as String), + mapper: (Map row) => Task( + row['id'] as int?, + row['message'] as String, + Timestamp( + createdAt: _dateTimeConverter + .decode(row['timestamp_created_at'] as int), + updatedAt: _dateTimeConverter + .decode(row['timestamp_updated_at'] as int))), queryableName: 'Task', isView: false); } @@ -185,3 +221,6 @@ class _$TaskDao extends TaskDao { await _taskDeletionAdapter.deleteList(tasks); } } + +// ignore_for_file: unused_element +final _dateTimeConverter = DateTimeConverter(); diff --git a/example/lib/date_time_converter.dart b/example/lib/date_time_converter.dart new file mode 100644 index 00000000..fcb5ac03 --- /dev/null +++ b/example/lib/date_time_converter.dart @@ -0,0 +1,9 @@ +import 'package:floor/floor.dart'; + +class DateTimeConverter extends TypeConverter { + @override + DateTime decode(int databaseValue) => DateTime.fromMillisecondsSinceEpoch(databaseValue); + + @override + int encode(DateTime value) => value.millisecondsSinceEpoch; +} diff --git a/example/lib/main.dart b/example/lib/main.dart index dcb0793c..624baedd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,7 @@ import 'package:example/database.dart'; import 'package:example/task.dart'; import 'package:example/task_dao.dart'; +import 'package:example/timestamp.dart'; import 'package:flutter/material.dart'; Future main() async { @@ -177,7 +178,12 @@ class TasksTextField extends StatelessWidget { if (message.trim().isEmpty) { _textEditingController.clear(); } else { - final task = Task(null, message); + final task = Task(null, message, + Timestamp( + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ); await dao.insertTask(task); _textEditingController.clear(); } diff --git a/example/lib/task.dart b/example/lib/task.dart index b570c8f9..d2bec3dc 100644 --- a/example/lib/task.dart +++ b/example/lib/task.dart @@ -1,3 +1,4 @@ +import 'package:example/timestamp.dart'; import 'package:floor/floor.dart'; @entity @@ -7,7 +8,9 @@ class Task { final String message; - Task(this.id, this.message); + final Timestamp timestamp; + + Task(this.id, this.message, this.timestamp); @override bool operator ==(Object other) => diff --git a/example/lib/timestamp.dart b/example/lib/timestamp.dart new file mode 100644 index 00000000..43c84d53 --- /dev/null +++ b/example/lib/timestamp.dart @@ -0,0 +1,28 @@ +import 'package:floor/floor.dart'; + +@Embed() +class Timestamp { + @ColumnInfo(name: 'created_at') + final DateTime createdAt; + + @ColumnInfo(name: 'updated_at') + final DateTime updatedAt; + + Timestamp({required this.createdAt, required this.updatedAt}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Timestamp && + runtimeType == other.runtimeType && + createdAt == other.createdAt && + updatedAt == other.updatedAt; + + @override + int get hashCode => createdAt.hashCode ^ updatedAt.hashCode; + + @override + String toString() { + return 'Timestamp{createdAt: $createdAt, updatedAt: $updatedAt}'; + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index a54ff6cf..5227c533 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -28,7 +28,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" boolean_selector: dependency: transitive description: @@ -70,7 +70,7 @@ packages: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.3" build_runner_core: dependency: transitive description: @@ -91,7 +91,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.5" + version: "8.0.6" characters: dependency: transitive description: @@ -161,7 +161,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" fake_async: dependency: transitive description: @@ -182,7 +182,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "6.1.0" + version: "6.1.1" fixnum: dependency: transitive description: @@ -196,7 +196,7 @@ packages: path: "../floor" relative: true source: path - version: "1.0.1" + version: "1.1.0" floor_annotation: dependency: transitive description: @@ -210,7 +210,7 @@ packages: path: "../floor_generator" relative: true source: path - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -221,6 +221,13 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" glob: dependency: transitive description: @@ -353,7 +360,7 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.4" shelf_web_socket: dependency: transitive description: @@ -379,7 +386,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sqflite: dependency: transitive description: @@ -407,7 +414,7 @@ packages: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.1" stack_trace: dependency: transitive description: @@ -463,7 +470,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.3.0" timing: dependency: transitive description: @@ -505,7 +512,7 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" yaml: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 89015152..b26d6c8e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: sdk: flutter dev_dependencies: - build_runner: ^2.0.0 + build_runner: ^2.0.3 floor_generator: path: ../floor_generator/ flutter_test: diff --git a/floor_annotation/lib/floor_annotation.dart b/floor_annotation/lib/floor_annotation.dart index c97fef74..90e23134 100644 --- a/floor_annotation/lib/floor_annotation.dart +++ b/floor_annotation/lib/floor_annotation.dart @@ -5,6 +5,7 @@ export 'src/dao.dart'; export 'src/database.dart'; export 'src/database_view.dart'; export 'src/delete.dart'; +export 'src/embed.dart'; export 'src/entity.dart'; export 'src/foreign_key.dart'; export 'src/fts.dart'; diff --git a/floor_annotation/lib/src/database.dart b/floor_annotation/lib/src/database.dart index d3bf873e..ec07a22d 100644 --- a/floor_annotation/lib/src/database.dart +++ b/floor_annotation/lib/src/database.dart @@ -6,6 +6,9 @@ class Database { /// The entities the database manages. final List entities; + /// The embeds the database manages. + final List embeds; + /// The views the database manages. final List views; @@ -13,6 +16,7 @@ class Database { const Database({ required this.version, required this.entities, + this.embeds = const [], this.views = const [], }); } diff --git a/floor_annotation/lib/src/embed.dart b/floor_annotation/lib/src/embed.dart new file mode 100644 index 00000000..67258c69 --- /dev/null +++ b/floor_annotation/lib/src/embed.dart @@ -0,0 +1,5 @@ +class Embed { + const Embed(); +} + +const embed = Embed(); diff --git a/floor_generator/lib/misc/constants.dart b/floor_generator/lib/misc/constants.dart index 689c1f7e..71c50a7e 100644 --- a/floor_generator/lib/misc/constants.dart +++ b/floor_generator/lib/misc/constants.dart @@ -4,6 +4,7 @@ abstract class AnnotationField { static const onConflict = 'onConflict'; static const databaseVersion = 'version'; + static const databaseEmbeds = 'embeds'; static const databaseEntities = 'entities'; static const databaseViews = 'views'; diff --git a/floor_generator/lib/misc/extension/embeds_extension.dart b/floor_generator/lib/misc/extension/embeds_extension.dart new file mode 100644 index 00000000..2f25dd73 --- /dev/null +++ b/floor_generator/lib/misc/extension/embeds_extension.dart @@ -0,0 +1,35 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; +import 'package:floor_generator/value_object/embed.dart'; +import 'package:floor_generator/value_object/type_converter.dart'; +import 'package:source_gen/source_gen.dart'; + +extension EmbedsExtension on Iterable { + // /// Returns the [Embed] in the closest [TypeConverterScope] or null + // Embed? get closestOrNull { + // return sortedByDescending((embed) => embed.scope.index) + // .firstOrNull; + // } + + /// Returns the [Embed] in the closest [TypeConverterScope] for + /// [dartType] or null + Embed? getClosestOrNull(DartType dartType) { + return toList() + .firstWhereOrNull( + (embed) => embed.classElement.name == dartType.toString()); + } + + // /// Returns the [Embed] in the closest [TypeConverterScope] for + // /// [dartType] + // Embed getClosest(DartType dartType) { + // final closest = getClosestOrNull(dartType); + // if (closest == null) { + // throw InvalidGenerationSourceError( + // 'Column type is not supported for $dartType', + // todo: 'Either use a supported type or supply a type converter.', + // ); + // } + // return closest; + // } +} diff --git a/floor_generator/lib/misc/extension/field_element_extension.dart b/floor_generator/lib/misc/extension/field_element_extension.dart new file mode 100644 index 00000000..d8e9f4e6 --- /dev/null +++ b/floor_generator/lib/misc/extension/field_element_extension.dart @@ -0,0 +1,14 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_annotation/floor_annotation.dart' as annotations; +import 'package:floor_generator/misc/type_utils.dart'; + +extension FieldElementExtension on FieldElement { + bool shouldBeIncluded() { + final isIgnored = hasAnnotation(annotations.ignore.runtimeType); + return !(isStatic || isSynthetic || isIgnored); + } + + bool get isEmbedded { + return (type.element?.hasAnnotation(annotations.Embed) ?? false) && type.element is ClassElement; + } +} diff --git a/floor_generator/lib/processor/database_processor.dart b/floor_generator/lib/processor/database_processor.dart index f088d351..643d326a 100644 --- a/floor_generator/lib/processor/database_processor.dart +++ b/floor_generator/lib/processor/database_processor.dart @@ -6,12 +6,14 @@ import 'package:floor_generator/misc/extension/set_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/dao_processor.dart'; +import 'package:floor_generator/processor/embed_processor.dart'; import 'package:floor_generator/processor/entity_processor.dart'; import 'package:floor_generator/processor/error/database_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/processor/view_processor.dart'; import 'package:floor_generator/value_object/dao_getter.dart'; import 'package:floor_generator/value_object/database.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:floor_generator/value_object/queryable.dart'; import 'package:floor_generator/value_object/type_converter.dart'; @@ -31,8 +33,9 @@ class DatabaseProcessor extends Processor { final databaseName = _classElement.displayName; final databaseTypeConverters = _classElement.getTypeConverters(TypeConverterScope.database); - final entities = _getEntities(_classElement, databaseTypeConverters); - final views = _getViews(_classElement, databaseTypeConverters); + final embeds = _getEmbeds(_classElement, databaseTypeConverters); + final entities = _getEntities(_classElement, databaseTypeConverters, embeds); + final views = _getViews(_classElement, databaseTypeConverters, embeds); final daoGetters = _getDaoGetters( databaseName, entities, @@ -102,9 +105,34 @@ class DatabaseProcessor extends Processor { classElement.isAbstract; } + Set _getEmbeds( + final ClassElement databaseClassElement, + final Set typeConverters, + ) { + final entities = _classElement + .getAnnotation(annotations.Database) + ?.getField(AnnotationField.databaseEmbeds) + ?.toListValue() + ?.mapNotNull((object) => object.toTypeValue()?.element) + .whereType() + .where(_isEmbed) + .map((classElement) => EmbedProcessor( + classElement, + typeConverters, + ).process()) + .toSet(); + + if (entities == null || entities.isEmpty) { + throw _processorError.noEntitiesDefined; + } + + return entities; + } + List _getEntities( final ClassElement databaseClassElement, final Set typeConverters, + final Set embedConverters, ) { final entities = _classElement .getAnnotation(annotations.Database) @@ -116,6 +144,7 @@ class DatabaseProcessor extends Processor { .map((classElement) => EntityProcessor( classElement, typeConverters, + embedConverters, ).process()) .toList(); @@ -129,20 +158,21 @@ class DatabaseProcessor extends Processor { List _getViews( final ClassElement databaseClassElement, final Set typeConverters, + final Set embedConverters, ) { return _classElement - .getAnnotation(annotations.Database) - ?.getField(AnnotationField.databaseViews) - ?.toListValue() - ?.mapNotNull((object) => object.toTypeValue()?.element) - .whereType() - .where(_isView) - .map((classElement) => ViewProcessor( - classElement, - typeConverters, - ).process()) - .toList() ?? - []; + .getAnnotation(annotations.Database) + ?.getField(AnnotationField.databaseViews) + ?.toListValue() + ?.mapNotNull((object) => object.toTypeValue()?.element) + .whereType() + .where(_isView) + .map((classElement) => ViewProcessor( + classElement, + typeConverters, + embedConverters, + ).process()) + .toList() ?? []; } Set _getAllTypeConverters( @@ -169,6 +199,11 @@ class DatabaseProcessor extends Processor { fieldTypeConverters; } + bool _isEmbed(final ClassElement classElement) { + return classElement.hasAnnotation(annotations.Embed) && + !classElement.isAbstract; + } + bool _isEntity(final ClassElement classElement) { return classElement.hasAnnotation(annotations.Entity) && !classElement.isAbstract; diff --git a/floor_generator/lib/processor/embed_processor.dart b/floor_generator/lib/processor/embed_processor.dart new file mode 100644 index 00000000..31aa6597 --- /dev/null +++ b/floor_generator/lib/processor/embed_processor.dart @@ -0,0 +1,32 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/misc/extension/field_element_extension.dart'; +import 'package:floor_generator/misc/extension/type_converters_extension.dart'; +import 'package:floor_generator/processor/field_processor.dart'; +import 'package:floor_generator/processor/processor.dart'; +import 'package:floor_generator/value_object/embed.dart'; +import 'package:floor_generator/value_object/field.dart'; +import 'package:floor_generator/value_object/type_converter.dart'; + +class EmbedProcessor extends Processor { + final ClassElement _classElement; + Set typeConverters; + + EmbedProcessor(this._classElement, this.typeConverters); + + @override + Embed process() { + return Embed( + _classElement, + _getFields(), + ); + } + + List _getFields() { + final fields = _classElement.fields + .where((fieldElement) => fieldElement.shouldBeIncluded()) + .map((field) => FieldProcessor(field, typeConverters.getClosestOrNull(field.type), null).process()) + .toList(); + + return fields; + } +} diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index a4f59dd8..470834e1 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -4,12 +4,14 @@ import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; +import 'package:floor_generator/misc/extension/field_element_extension.dart'; import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/error/entity_processor_error.dart'; import 'package:floor_generator/processor/queryable_processor.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:floor_generator/value_object/field.dart'; import 'package:floor_generator/value_object/foreign_key.dart'; @@ -24,8 +26,9 @@ class EntityProcessor extends QueryableProcessor { EntityProcessor( final ClassElement classElement, final Set typeConverters, - ) : _processorError = EntityProcessorError(classElement), - super(classElement, typeConverters); + final Set embedConverters, + ) : _processorError = EntityProcessorError(classElement), + super(classElement, typeConverters, embedConverters); @override Entity process() { @@ -262,17 +265,34 @@ class EntityProcessor extends QueryableProcessor { false; } + void _processFields(final Map map, final List fields, { + String prefix1 = '', + String prefix = '', + }) { + for (final field in fields) { + if (field.embedConverter != null) { + _processFields( + map, field.embedConverter?.fields ?? [], + prefix1: '$prefix1${field.name}_', + prefix: '$prefix${field.fieldElement.name}.', + ); + } else { + map['$prefix1${field.columnName}'] = _getAttributeValue(field, prefix: prefix); + } + } + } + String _getValueMapping(final List fields) { - final keyValueList = fields.map((field) { - final columnName = field.columnName; - final attributeValue = _getAttributeValue(field); - return "'$columnName': $attributeValue"; - }).toList(); + final Map map = {}; + _processFields(map, fields); + final keyValueList = map.entries + .map((entry) => "'${entry.key}': ${entry.value}") + .toList(); return '{${keyValueList.join(', ')}}'; } - String _getAttributeValue(final Field field) { + String _getAttributeValue(final Field field, {String prefix = ''}) { final fieldElement = field.fieldElement; final parameterName = fieldElement.displayName; final fieldType = fieldElement.type; @@ -280,14 +300,14 @@ class EntityProcessor extends QueryableProcessor { String attributeValue; if (fieldType.isDefaultSqlType) { - attributeValue = 'item.$parameterName'; + attributeValue = 'item.$prefix$parameterName'; } else { final typeConverter = [ ...queryableTypeConverters, field.typeConverter, ].whereNotNull().getClosest(fieldType); attributeValue = - '_${typeConverter.name.decapitalize()}.encode(item.$parameterName)'; + '_${typeConverter.name.decapitalize()}.encode(item.$prefix$parameterName)'; } if (fieldType.isDartCoreBool) { diff --git a/floor_generator/lib/processor/field_processor.dart b/floor_generator/lib/processor/field_processor.dart index 760b658e..b210a5ba 100644 --- a/floor_generator/lib/processor/field_processor.dart +++ b/floor_generator/lib/processor/field_processor.dart @@ -8,6 +8,7 @@ import 'package:floor_generator/misc/extension/type_converter_element_extension. import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/processor.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/field.dart'; import 'package:floor_generator/value_object/type_converter.dart'; import 'package:source_gen/source_gen.dart'; @@ -15,12 +16,13 @@ import 'package:source_gen/source_gen.dart'; class FieldProcessor extends Processor { final FieldElement _fieldElement; final TypeConverter? _typeConverter; + final Embed? _embedConverter; FieldProcessor( - final FieldElement fieldElement, - final TypeConverter? typeConverter, - ) : _fieldElement = fieldElement, - _typeConverter = typeConverter; + this._fieldElement, + this._typeConverter, + this._embedConverter, + ); @override Field process() { @@ -37,8 +39,9 @@ class FieldProcessor extends Processor { name, columnName, isNullable, - _getSqlType(typeConverter), + _getSqlType(typeConverter, _embedConverter), typeConverter, + _embedConverter, ); } @@ -52,12 +55,14 @@ class FieldProcessor extends Processor { : name; } - String _getSqlType(final TypeConverter? typeConverter) { + String _getSqlType(final TypeConverter? typeConverter, final Embed? embedConverter) { final type = _fieldElement.type; if (type.isDefaultSqlType) { return type.asSqlType(); } else if (typeConverter != null) { return typeConverter.databaseType.asSqlType(); + } else if (embedConverter != null) { + return ''; } else { throw InvalidGenerationSourceError( 'Column type is not supported for $type.', diff --git a/floor_generator/lib/processor/queryable_processor.dart b/floor_generator/lib/processor/queryable_processor.dart index 95b0a13a..e3fa4f64 100644 --- a/floor_generator/lib/processor/queryable_processor.dart +++ b/floor_generator/lib/processor/queryable_processor.dart @@ -1,8 +1,9 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; -import 'package:floor_annotation/floor_annotation.dart' as annotations; import 'package:floor_generator/misc/extension/dart_type_extension.dart'; +import 'package:floor_generator/misc/extension/embeds_extension.dart'; +import 'package:floor_generator/misc/extension/field_element_extension.dart'; import 'package:floor_generator/misc/extension/set_extension.dart'; import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; @@ -11,6 +12,7 @@ import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/error/queryable_processor_error.dart'; import 'package:floor_generator/processor/field_processor.dart'; import 'package:floor_generator/processor/processor.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/field.dart'; import 'package:floor_generator/value_object/queryable.dart'; import 'package:floor_generator/value_object/type_converter.dart'; @@ -25,13 +27,16 @@ abstract class QueryableProcessor extends Processor { final Set queryableTypeConverters; + final Set embedConverters; + @protected QueryableProcessor( this.classElement, final Set typeConverters, - ) : _queryableProcessorError = QueryableProcessorError(classElement), - queryableTypeConverters = typeConverters + - classElement.getTypeConverters(TypeConverterScope.queryable); + this.embedConverters, + ) : _queryableProcessorError = QueryableProcessorError(classElement), + queryableTypeConverters = typeConverters + + classElement.getTypeConverters(TypeConverterScope.queryable); @protected List getFields() { @@ -41,22 +46,26 @@ abstract class QueryableProcessor extends Processor { final fields = [ ...classElement.fields, ...classElement.allSupertypes.expand((type) => type.element.fields), - ]; - - return fields - .where((fieldElement) => fieldElement.shouldBeIncluded()) - .map((field) { - final typeConverter = - queryableTypeConverters.getClosestOrNull(field.type); - return FieldProcessor(field, typeConverter).process(); + ].where((fieldElement) => fieldElement.shouldBeIncluded()); + + return fields.map((field) { + if (field.isEmbedded) { + return FieldProcessor(field, null, embedConverters.getClosestOrNull(field.type)).process(); + } else { + return FieldProcessor(field, queryableTypeConverters.getClosestOrNull(field.type), null).process(); + } }).toList(); } @protected String getConstructor(final List fields) { + return _getConstructor(classElement, fields); + } + + String _getConstructor(ClassElement classElement, final List fields, {String prefix = ''}) { final constructorParameters = classElement.constructors.first.parameters; final parameterValues = constructorParameters - .map((parameterElement) => _getParameterValue(parameterElement, fields)) + .map((parameterElement) => _getParameterValue(parameterElement, fields, prefix: prefix)) .where((parameterValue) => parameterValue != null) .join(', '); @@ -66,42 +75,48 @@ abstract class QueryableProcessor extends Processor { /// Returns `null` whenever field is @ignored String? _getParameterValue( final ParameterElement parameterElement, - final List fields, - ) { + final List fields, { + final String prefix = '', + }) { final parameterName = parameterElement.displayName; - final field = - // null whenever field is @ignored - fields.firstWhereOrNull((field) => field.name == parameterName); + + // null whenever field is @ignored + final field = fields.firstWhereOrNull((field) => field.fieldElement.displayName == parameterName); if (field != null) { - final databaseValue = "row['${field.columnName}']"; + if (field is Field) { + final databaseValue = "row['$prefix${field.columnName}']"; - String parameterValue; + String parameterValue; + if (parameterElement.type.isDefaultSqlType) { + parameterValue = databaseValue.cast( + parameterElement.type, + parameterElement, + ); + } else if (field.embedConverter != null) { + parameterValue = _getConstructor(field.embedConverter!.classElement, field.embedConverter!.fields, prefix: '$prefix${field.name}_'); + } else { + final typeConverter = [ + ...queryableTypeConverters, + field.typeConverter, + ].whereNotNull().getClosest(parameterElement.type); - if (parameterElement.type.isDefaultSqlType) { - parameterValue = databaseValue.cast( - parameterElement.type, - parameterElement, - ); - } else { - final typeConverter = [...queryableTypeConverters, field.typeConverter] - .whereNotNull() - .getClosest(parameterElement.type); - final castedDatabaseValue = databaseValue.cast( - typeConverter.databaseType, - parameterElement, - ); - - parameterValue = - '_${typeConverter.name.decapitalize()}.decode($castedDatabaseValue)'; - } + final castedDatabaseValue = databaseValue.cast( + typeConverter.databaseType, + parameterElement, + ); - if (parameterElement.isNamed) { - return '$parameterName: $parameterValue'; + parameterValue = '_${typeConverter.name.decapitalize()}.decode($castedDatabaseValue)'; + } + + if (parameterElement.isNamed) { + return '$parameterName: $parameterValue'; + } + return parameterValue; // also covers positional parameter + } else if (field is Embed) { + // return _getConstructor(field.classElement, [...field.fields, ...field.children]); } - return parameterValue; // also covers positional parameter - } else { - return null; } + return null; } } @@ -130,10 +145,3 @@ extension on String { } } } - -extension on FieldElement { - bool shouldBeIncluded() { - final isIgnored = hasAnnotation(annotations.ignore.runtimeType); - return !(isStatic || isSynthetic || isIgnored); - } -} diff --git a/floor_generator/lib/processor/view_processor.dart b/floor_generator/lib/processor/view_processor.dart index b30d9bd5..f7e2d8cb 100644 --- a/floor_generator/lib/processor/view_processor.dart +++ b/floor_generator/lib/processor/view_processor.dart @@ -4,6 +4,7 @@ import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/error/view_processor_error.dart'; import 'package:floor_generator/processor/queryable_processor.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/type_converter.dart'; import 'package:floor_generator/value_object/view.dart'; @@ -13,8 +14,9 @@ class ViewProcessor extends QueryableProcessor { ViewProcessor( final ClassElement classElement, final Set typeConverters, + final Set embedConverters, ) : _processorError = ViewProcessorError(classElement), - super(classElement, typeConverters); + super(classElement, typeConverters, embedConverters); @override View process() { diff --git a/floor_generator/lib/value_object/embed.dart b/floor_generator/lib/value_object/embed.dart new file mode 100644 index 00000000..2348b062 --- /dev/null +++ b/floor_generator/lib/value_object/embed.dart @@ -0,0 +1,25 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:collection/collection.dart'; +import 'package:floor_generator/value_object/field.dart'; + +class Embed { + final ClassElement classElement; + final List fields; + + Embed(this.classElement, this.fields); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Embed && + runtimeType == other.runtimeType && + const ListEquality().equals(fields, other.fields); + + @override + int get hashCode => classElement.hashCode ^ fields.hashCode; + + @override + String toString() { + return 'Embed{classElement: $classElement, fields: $fields}'; + } +} diff --git a/floor_generator/lib/value_object/field.dart b/floor_generator/lib/value_object/field.dart index 0f265ed4..c48f6526 100644 --- a/floor_generator/lib/value_object/field.dart +++ b/floor_generator/lib/value_object/field.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/type_converter.dart'; /// Represents an Entity field and thus a table column. @@ -9,6 +10,7 @@ class Field { final bool isNullable; final String sqlType; final TypeConverter? typeConverter; + final Embed? embedConverter; Field( this.fieldElement, @@ -17,6 +19,7 @@ class Field { this.isNullable, this.sqlType, this.typeConverter, + this.embedConverter, ); /// The database column definition. diff --git a/floor_generator/pubspec.lock b/floor_generator/pubspec.lock index b814f358..b2129ec6 100644 --- a/floor_generator/pubspec.lock +++ b/floor_generator/pubspec.lock @@ -28,7 +28,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.1" boolean_selector: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "8.0.5" + version: "8.0.6" characters: dependency: transitive description: @@ -182,7 +182,7 @@ packages: name: frontend_server_client url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" glob: dependency: transitive description: @@ -329,7 +329,7 @@ packages: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.4" shelf_packages_handler: dependency: transitive description: @@ -483,7 +483,7 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" webkit_inspection_protocol: dependency: transitive description: @@ -499,4 +499,4 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <2.14.0" + dart: ">=2.12.0 <3.0.0" From 61e0cca8c0b445c1ad769ef5503085db4dc5dfd9 Mon Sep 17 00:00:00 2001 From: Marcos Kobuchi Date: Wed, 19 May 2021 21:47:52 -0300 Subject: [PATCH 2/2] Fix naming columns --- example/lib/database.g.dart | 2 +- example/lib/task.dart | 1 + .../lib/misc/extension/embeds_extension.dart | 2 -- .../lib/processor/embed_processor.dart | 2 +- .../lib/processor/entity_processor.dart | 18 +++++++++--------- .../error/change_method_processor_error.dart | 2 +- .../lib/processor/field_processor.dart | 3 +-- .../lib/processor/query_processor.dart | 2 +- .../lib/processor/queryable_processor.dart | 6 ++++-- floor_generator/lib/value_object/entity.dart | 14 +++++++++++++- floor_generator/lib/value_object/field.dart | 18 ++++++++++++++++++ 11 files changed, 50 insertions(+), 20 deletions(-) diff --git a/example/lib/database.g.dart b/example/lib/database.g.dart index de2e142b..033c46ae 100644 --- a/example/lib/database.g.dart +++ b/example/lib/database.g.dart @@ -81,7 +81,7 @@ class _$FlutterDatabase extends FlutterDatabase { }, onCreate: (database, version) async { await database.execute( - 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT NOT NULL, `timestamp` NOT NULL)'); + 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT NOT NULL, `timestamp_created_at` INTEGER NOT NULL, `timestamp_updated_at` INTEGER NOT NULL)'); await callback?.onCreate?.call(database, version); }, diff --git a/example/lib/task.dart b/example/lib/task.dart index d2bec3dc..2ce8825e 100644 --- a/example/lib/task.dart +++ b/example/lib/task.dart @@ -8,6 +8,7 @@ class Task { final String message; + // @ColumnInfo(name: '') final Timestamp timestamp; Task(this.id, this.message, this.timestamp); diff --git a/floor_generator/lib/misc/extension/embeds_extension.dart b/floor_generator/lib/misc/extension/embeds_extension.dart index 2f25dd73..a8f60c1e 100644 --- a/floor_generator/lib/misc/extension/embeds_extension.dart +++ b/floor_generator/lib/misc/extension/embeds_extension.dart @@ -1,9 +1,7 @@ import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; -import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/type_converter.dart'; -import 'package:source_gen/source_gen.dart'; extension EmbedsExtension on Iterable { // /// Returns the [Embed] in the closest [TypeConverterScope] or null diff --git a/floor_generator/lib/processor/embed_processor.dart b/floor_generator/lib/processor/embed_processor.dart index 31aa6597..13a88189 100644 --- a/floor_generator/lib/processor/embed_processor.dart +++ b/floor_generator/lib/processor/embed_processor.dart @@ -9,7 +9,7 @@ import 'package:floor_generator/value_object/type_converter.dart'; class EmbedProcessor extends Processor { final ClassElement _classElement; - Set typeConverters; + final Set typeConverters; EmbedProcessor(this._classElement, this.typeConverters); diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index 470834e1..03ca8da5 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -4,7 +4,6 @@ import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; -import 'package:floor_generator/misc/extension/field_element_extension.dart'; import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; @@ -266,18 +265,19 @@ class EntityProcessor extends QueryableProcessor { } void _processFields(final Map map, final List fields, { - String prefix1 = '', - String prefix = '', + String columnNamePrefix = '', + String parameterPrefix = '', }) { for (final field in fields) { if (field.embedConverter != null) { + final embedVar = field.columnName.isEmpty ? '' : '${field.columnName}_'; _processFields( map, field.embedConverter?.fields ?? [], - prefix1: '$prefix1${field.name}_', - prefix: '$prefix${field.fieldElement.name}.', + columnNamePrefix: '$columnNamePrefix$embedVar', + parameterPrefix: '$parameterPrefix${field.fieldElement.name}.', ); } else { - map['$prefix1${field.columnName}'] = _getAttributeValue(field, prefix: prefix); + map['$columnNamePrefix${field.columnName}'] = _getAttributeValue(field, parameterPrefix); } } } @@ -292,7 +292,7 @@ class EntityProcessor extends QueryableProcessor { return '{${keyValueList.join(', ')}}'; } - String _getAttributeValue(final Field field, {String prefix = ''}) { + String _getAttributeValue(final Field field, String parameterPrefix) { final fieldElement = field.fieldElement; final parameterName = fieldElement.displayName; final fieldType = fieldElement.type; @@ -300,14 +300,14 @@ class EntityProcessor extends QueryableProcessor { String attributeValue; if (fieldType.isDefaultSqlType) { - attributeValue = 'item.$prefix$parameterName'; + attributeValue = 'item.$parameterPrefix$parameterName'; } else { final typeConverter = [ ...queryableTypeConverters, field.typeConverter, ].whereNotNull().getClosest(fieldType); attributeValue = - '_${typeConverter.name.decapitalize()}.encode(item.$prefix$parameterName)'; + '_${typeConverter.name.decapitalize()}.encode(item.$parameterPrefix$parameterName)'; } if (fieldType.isDartCoreBool) { diff --git a/floor_generator/lib/processor/error/change_method_processor_error.dart b/floor_generator/lib/processor/error/change_method_processor_error.dart index 2d52d104..b04e1d90 100644 --- a/floor_generator/lib/processor/error/change_method_processor_error.dart +++ b/floor_generator/lib/processor/error/change_method_processor_error.dart @@ -1,7 +1,7 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/constants.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations show OnConflictStrategy; +import 'package:floor_generator/misc/constants.dart'; import 'package:source_gen/source_gen.dart'; class ChangeMethodProcessorError { diff --git a/floor_generator/lib/processor/field_processor.dart b/floor_generator/lib/processor/field_processor.dart index b210a5ba..8a644b04 100644 --- a/floor_generator/lib/processor/field_processor.dart +++ b/floor_generator/lib/processor/field_processor.dart @@ -50,8 +50,7 @@ class FieldProcessor extends Processor { ? _fieldElement .getAnnotation(annotations.ColumnInfo) ?.getField(AnnotationField.columnInfoName) - ?.toStringValue() ?? - name + ?.toStringValue() ?? name : name; } diff --git a/floor_generator/lib/processor/query_processor.dart b/floor_generator/lib/processor/query_processor.dart index 342a9e54..346ccfdd 100644 --- a/floor_generator/lib/processor/query_processor.dart +++ b/floor_generator/lib/processor/query_processor.dart @@ -1,5 +1,5 @@ -import 'package:floor_generator/misc/extension/dart_type_extension.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/misc/extension/dart_type_extension.dart'; import 'package:floor_generator/processor/error/query_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/query.dart'; diff --git a/floor_generator/lib/processor/queryable_processor.dart b/floor_generator/lib/processor/queryable_processor.dart index e3fa4f64..437870b9 100644 --- a/floor_generator/lib/processor/queryable_processor.dart +++ b/floor_generator/lib/processor/queryable_processor.dart @@ -77,7 +77,8 @@ abstract class QueryableProcessor extends Processor { final ParameterElement parameterElement, final List fields, { final String prefix = '', - }) { + } + ) { final parameterName = parameterElement.displayName; // null whenever field is @ignored @@ -93,7 +94,8 @@ abstract class QueryableProcessor extends Processor { parameterElement, ); } else if (field.embedConverter != null) { - parameterValue = _getConstructor(field.embedConverter!.classElement, field.embedConverter!.fields, prefix: '$prefix${field.name}_'); + final embedVar = field.columnName.isEmpty ? '' : '${field.columnName}_'; + parameterValue = _getConstructor(field.embedConverter!.classElement, field.embedConverter!.fields, prefix: '$prefix$embedVar'); } else { final typeConverter = [ ...queryableTypeConverters, diff --git a/floor_generator/lib/value_object/entity.dart b/floor_generator/lib/value_object/entity.dart index fda23624..58bc9d21 100644 --- a/floor_generator/lib/value_object/entity.dart +++ b/floor_generator/lib/value_object/entity.dart @@ -30,7 +30,7 @@ class Entity extends Queryable { ) : super(classElement, name, fields, constructor); String getCreateTableStatement() { - final databaseDefinition = fields.map((field) { + final databaseDefinition = _getFields(fields).map((field) { final autoIncrement = primaryKey.fields.contains(field) && primaryKey.autoGenerateId; return field.getDatabaseDefinition(autoIncrement); @@ -57,6 +57,18 @@ class Entity extends Queryable { } } + List _getFields(List fields, {String columnNamePrefix = ''}) { + return fields.map((field) { + final embedConverter = field.embedConverter; + if (embedConverter != null) { + final embedVar = field.columnName.isEmpty ? '' : '${field.columnName}_'; + return _getFields(embedConverter.fields, columnNamePrefix: embedVar); + } else { + return [field.copyWith(columnNamePrefix: columnNamePrefix)]; + } + }).reduce((value, element) => value + element); + } + String? _createPrimaryKeyDefinition() { if (primaryKey.autoGenerateId) { return null; diff --git a/floor_generator/lib/value_object/field.dart b/floor_generator/lib/value_object/field.dart index c48f6526..cd8b6669 100644 --- a/floor_generator/lib/value_object/field.dart +++ b/floor_generator/lib/value_object/field.dart @@ -1,6 +1,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:floor_generator/value_object/embed.dart'; import 'package:floor_generator/value_object/type_converter.dart'; +import 'package:source_gen/source_gen.dart'; /// Represents an Entity field and thus a table column. class Field { @@ -24,6 +25,13 @@ class Field { /// The database column definition. String getDatabaseDefinition(final bool autoGenerate) { + if (embedConverter != null) { + throw InvalidGenerationSourceError( + 'You ', + todo: 'Either make to use a supported type or supply a type converter.', + element: fieldElement, + ); + } final columnSpecification = StringBuffer(); if (autoGenerate) { @@ -36,6 +44,16 @@ class Field { return '`$columnName` $sqlType$columnSpecification'; } + Field copyWith({ + String columnNamePrefix = '', + }) => Field(fieldElement, + name, + '$columnNamePrefix$columnName', + isNullable, + sqlType, + typeConverter, embedConverter, + ); + @override bool operator ==(Object other) => identical(this, other) ||