From 45acb13b5d88c16128693d08586e04b749637899 Mon Sep 17 00:00:00 2001 From: Seyed Abbas Ghomi Date: Thu, 7 Nov 2019 15:58:55 +0330 Subject: [PATCH] transient annotation is implemented transient annotation is implemented to support the transient fields in entities these fields should not be persisted to data tables in project's database and they only serve the data integrity --- example/lib/task.dart | 5 +- floor_annotation/lib/floor_annotation.dart | 1 + floor_annotation/lib/src/transient.dart | 7 ++ .../lib/processor/entity_processor.dart | 22 +++-- .../lib/value_object/transient.dart | 21 +++++ .../test/processor/entity_processor_test.dart | 39 ++++++++ floor_test/test/database_test.dart | 94 +++++++++---------- floor_test/test/model/dog.dart | 14 ++- floor_test/test/model/person.dart | 17 +++- 9 files changed, 157 insertions(+), 63 deletions(-) create mode 100644 floor_annotation/lib/src/transient.dart create mode 100644 floor_generator/lib/value_object/transient.dart diff --git a/example/lib/task.dart b/example/lib/task.dart index 4e9c8bd8..747240b4 100644 --- a/example/lib/task.dart +++ b/example/lib/task.dart @@ -7,7 +7,10 @@ class Task { final String message; - Task(this.id, this.message); + @transient + String description; + + Task(this.id, this.message, [this.description='']); @override bool operator ==(Object other) => diff --git a/floor_annotation/lib/floor_annotation.dart b/floor_annotation/lib/floor_annotation.dart index 760a38d9..f7366eb7 100644 --- a/floor_annotation/lib/floor_annotation.dart +++ b/floor_annotation/lib/floor_annotation.dart @@ -12,4 +12,5 @@ export 'src/on_conflict_strategy.dart'; export 'src/primary_key.dart'; export 'src/query.dart'; export 'src/transaction.dart'; +export 'src/transient.dart'; export 'src/update.dart'; diff --git a/floor_annotation/lib/src/transient.dart b/floor_annotation/lib/src/transient.dart new file mode 100644 index 00000000..ae11ef39 --- /dev/null +++ b/floor_annotation/lib/src/transient.dart @@ -0,0 +1,7 @@ +/// Marks a field in an [Entity] as the transient. +class Transient { + const Transient(); +} + +/// Marks a field in an [Entity] as the transient. +const transient = Transient(); diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index 1f80fb4f..cb079a5b 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -30,16 +30,16 @@ class EntityProcessor extends Processor { @override Entity process() { final name = _getName(); - final fields = _getFields(); + final allFieldsButTransients = _getAllButTransientsFields(); return Entity( _classElement, name, - fields, - _getPrimaryKey(fields), + allFieldsButTransients, + _getPrimaryKey(allFieldsButTransients), _getForeignKeys(), - _getIndices(fields, name), - _getConstructor(fields), + _getIndices(allFieldsButTransients, name), + _getConstructor(allFieldsButTransients), ); } @@ -53,9 +53,9 @@ class EntityProcessor extends Processor { } @nonNull - List _getFields() { + List _getAllButTransientsFields() { return _classElement.fields - .where(_isNotHashCode) + .where(_isNotHashCode).where(_isNotTransient) .map((field) => FieldProcessor(field).process()) .toList(); } @@ -65,6 +65,12 @@ class EntityProcessor extends Processor { return fieldElement.displayName != 'hashCode'; } + @nonNull + bool _isNotTransient(final FieldElement fieldElement) { + return !typeChecker(annotations.Transient) + .hasAnnotationOfExact(fieldElement); + } + @nonNull List _getForeignKeys() { return _entityTypeChecker @@ -231,7 +237,7 @@ class EntityProcessor extends Processor { @nonNull String _getConstructor(final List fields) { final columnNames = fields.map((field) => field.columnName).toList(); - final constructorParameters = _classElement.constructors.first.parameters; + final constructorParameters = _classElement.constructors.first.parameters. where((f)=> columnNames.contains(f.name)).toList(); final parameterValues = []; diff --git a/floor_generator/lib/value_object/transient.dart b/floor_generator/lib/value_object/transient.dart new file mode 100644 index 00000000..f0e4787a --- /dev/null +++ b/floor_generator/lib/value_object/transient.dart @@ -0,0 +1,21 @@ +import 'package:collection/collection.dart'; +import 'package:floor_generator/value_object/field.dart'; + +/// Transient representation of a field in an Entity +class Transient { + final List fields; + + Transient(this.fields); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Transient && + runtimeType == other.runtimeType && + const ListEquality().equals(fields, other.fields); + + @override + String toString() { + return 'Transient{fields: $fields}'; + } +} diff --git a/floor_generator/test/processor/entity_processor_test.dart b/floor_generator/test/processor/entity_processor_test.dart index 510ed7c3..27da2326 100644 --- a/floor_generator/test/processor/entity_processor_test.dart +++ b/floor_generator/test/processor/entity_processor_test.dart @@ -128,6 +128,45 @@ void main() { expect(actual, equals(expected)); }); }); + + test('Should entity with transient field be not equals to floor processed entity', () async { + final classElement = await _createClassElement(''' + @entity + class Person { + @primaryKey + final int id; + + final String name; + + @transient + String nickName; + + Person(this.id, this.name, [this.nickName='']); + } + '''); + + final actual = EntityProcessor(classElement).process(); + + const name = 'Person'; + final fields = classElement.fields + .map((fieldElement) => FieldProcessor(fieldElement).process()) + .toList(); + final primaryKey = PrimaryKey([fields[0]], false); + const foreignKeys = []; + const indices = []; + const constructor = "Person(row['id'] as int, row['name'] as String)"; + final expected = Entity( + classElement, + name, + fields, + primaryKey, + foreignKeys, + indices, + constructor, + ); + + expect(actual.fields, isNot(equals(expected.fields))); + }); } Future _createClassElement(final String clazz) async { diff --git a/floor_test/test/database_test.dart b/floor_test/test/database_test.dart index 717cfaf2..dd14af8a 100644 --- a/floor_test/test/database_test.dart +++ b/floor_test/test/database_test.dart @@ -45,7 +45,7 @@ void main() { group('change single item', () { test('insert person', () async { - final person = Person(null, 'Simon'); + final person = Person(null, 'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = await personDao.findAllPersons(); @@ -54,7 +54,7 @@ void main() { }); test('delete person', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); await personDao.deletePerson(person); @@ -64,9 +64,9 @@ void main() { }); test('update person', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); - final updatedPerson = Person(person.id, _reverse(person.name)); + final updatedPerson = Person(person.id,_reverse(person.nickName),_reverse(person.alias), _reverse(person.name)); await personDao.updatePerson(updatedPerson); @@ -77,7 +77,7 @@ void main() { group('change multiple items', () { test('insert persons', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); @@ -86,7 +86,7 @@ void main() { }); test('delete persons', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); await personDao.deletePersons(persons); @@ -96,10 +96,10 @@ void main() { }); test('update persons', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); final updatedPersons = persons - .map((person) => Person(person.id, _reverse(person.name))) + .map((person) => Person(person.id,_reverse(person.nickName),_reverse(person.alias), _reverse(person.name))) .toList(); await personDao.updatePersons(updatedPersons); @@ -111,7 +111,7 @@ void main() { group('querying', () { test('query with two parameters (int, String)', () async { - final person = Person(1, 'Frank'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = await personDao.findPersonByIdAndName(1, 'Frank'); @@ -122,9 +122,9 @@ void main() { group('transaction', () { test('replace persons in transaction', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); - final newPersons = [Person(3, 'Paul'), Person(4, 'Karl')]; + final newPersons =[Person(3,'Mickey','Wick', 'Paul'), Person(4,'Raul','Nox', 'Karl')]; await personDao.replacePersons(newPersons); @@ -133,9 +133,9 @@ void main() { }); test('Reactivity is retained when using transactions', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); - final newPersons = [Person(3, 'Paul'), Person(4, 'Karl')]; + final newPersons =[Person(3,'Mickey','Wick', 'Paul'), Person(4,'Raul','Nox', 'Karl')]; final actual = personDao.findAllPersonsAsStream(); expect(actual, emits(persons)); @@ -147,7 +147,7 @@ void main() { group('change items and return int/list of int', () { test('insert person and return id of inserted item', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); final actual = await personDao.insertPersonWithReturn(person); @@ -155,7 +155,7 @@ void main() { }); test('insert persons and return ids of inserted items', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; final actual = await personDao.insertPersonsWithReturn(persons); @@ -164,9 +164,9 @@ void main() { }); test('update person and return 1 (affected row count)', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); - final updatedPerson = Person(person.id, _reverse(person.name)); + final updatedPerson = Person(person.id,_reverse(person.nickName),_reverse(person.alias), _reverse(person.name)); final actual = await personDao.updatePersonWithReturn(updatedPerson); @@ -176,10 +176,10 @@ void main() { }); test('update persons and return affected rows count', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); final updatedPersons = persons - .map((person) => Person(person.id, _reverse(person.name))) + .map((person) => Person(person.id,_reverse(person.nickName),_reverse(person.alias), _reverse(person.name))) .toList(); final actual = await personDao.updatePersonsWithReturn(updatedPersons); @@ -190,7 +190,7 @@ void main() { }); test('delete person and return 1 (affected row count)', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = await personDao.deletePersonWithReturn(person); @@ -199,7 +199,7 @@ void main() { }); test('delete persons and return affected rows count', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); final actual = await personDao.deletePersonsWithReturn(persons); @@ -210,15 +210,15 @@ void main() { group('foreign key', () { test('foreign key constraint failed exception', () { - final dog = Dog(null, 'Peter', 'Pete', 2); + final dog = Dog(null,'Brown','Fido', 'Peter', 'Pete', 2); expect(() => dogDao.insertDog(dog), _throwsDatabaseException); }); test('find dog for person', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2,'Brown' ,'Fido','Peter', 'Pete', person.id); await dogDao.insertDog(dog); final actual = await dogDao.findDogForPersonId(person.id); @@ -227,9 +227,9 @@ void main() { }); test('cascade delete dog on deletion of person', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2,'Brown' ,'Fido','Peter', 'Pete', person.id); await dogDao.insertDog(dog); await personDao.deletePerson(person); @@ -241,7 +241,7 @@ void main() { group('query with void return', () { test('delete all persons', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); await personDao.deleteAllPersons(); @@ -253,7 +253,7 @@ void main() { group('stream queries', () { test('initially emit persistent data', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = personDao.findAllPersonsAsStream(); @@ -263,7 +263,7 @@ void main() { group('insert change', () { test('find person by id as stream', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); final actual = personDao.findPersonByIdAsStream(person.id); @@ -272,7 +272,7 @@ void main() { }); test('find all persons as stream', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; final actual = personDao.findAllPersonsAsStream(); expect(actual, emits(>[])); @@ -282,8 +282,8 @@ void main() { }); test('initially emits persistent data then new', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - final persons2 = [Person(3, 'Paul'), Person(4, 'George')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; + final persons2 =[Person(3,'Mickey','Wick', 'Paul'), Person(4,'Raul','Nox', 'Karl')]; await personDao.insertPersons(persons); final actual = personDao.findAllPersonsAsStream(); @@ -296,21 +296,21 @@ void main() { group('update change', () { test('update item', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = personDao.findAllPersonsAsStream(); expect(actual, emits([person])); - final updatedPerson = Person(person.id, 'Frank'); + final updatedPerson = Person(person.id,'Rick','Rock', 'Frank'); await personDao.updatePerson(updatedPerson); expect(actual, emits([updatedPerson])); }); test('update items', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; final updatedPersons = persons - .map((person) => Person(person.id, _reverse(person.name))) + .map((person) => Person(person.id, _reverse(person.nickName),_reverse(person.alias),_reverse(person.name))) .toList(); await personDao.insertPersons(persons); @@ -324,7 +324,7 @@ void main() { group('deletion change', () { test('delete item', () async { - final person = Person(1, 'Simon'); + final person = Person(1,'Rick','Rock', 'Simon'); await personDao.insertPerson(person); final actual = personDao.findAllPersonsAsStream(); @@ -335,7 +335,7 @@ void main() { }); test('delete items', () async { - final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; + final persons = [Person(1,'Rick','Rock', 'Simon'), Person(2,'Joe','Vallery', 'Samuel')]; await personDao.insertPersons(persons); final actual = personDao.findAllPersonsAsStream(); @@ -349,9 +349,9 @@ void main() { group('IN clause', () { test('Find persons with IDs', () async { - final person1 = Person(1, 'Simon'); - final person2 = Person(2, 'Frank'); - final person3 = Person(3, 'Paul'); + final person1 = Person(1,'Rick','Rock', 'Simon'); + final person2 =Person(2,'Joe','Vallery', 'Samuel'); + final person3 = Person(3,'Mickey','Wick', 'Paul'); await personDao.insertPersons([person1, person2, person3]); final ids = [person1.id, person2.id]; @@ -361,9 +361,9 @@ void main() { }); test('Find persons with names', () async { - final person1 = Person(1, 'Simon'); - final person2 = Person(2, 'Simon'); - final person3 = Person(3, 'Paul'); + final person1 = Person(1,'Rick','Rock', 'Simon'); + final person2 =Person(2,'Joe','Vallery', 'Samuel'); + final person3 = Person(3,'Mickey','Wick', 'Paul'); await personDao.insertPersons([person1, person2, person3]); final names = [person1.name, person2.name]; @@ -376,9 +376,9 @@ void main() { group('LIKE operator', () { test('Find persons with name LIKE', () async { final persons = [ - Person(1, 'Simon'), - Person(2, 'Frank'), - Person(3, 'Paul') + Person(1,'Rick','Rock', 'Simon'), + Person(2,'Joe','Vallery', 'Samuel'), + Person(3,'Mickey','Wick', 'Paul') ]; await personDao.insertPersons(persons); diff --git a/floor_test/test/model/dog.dart b/floor_test/test/model/dog.dart index 35066a39..30c0c132 100644 --- a/floor_test/test/model/dog.dart +++ b/floor_test/test/model/dog.dart @@ -17,6 +17,12 @@ class Dog { @primaryKey final int id; + @transient + final String color; + + @transient + final String alias; + final String name; @ColumnInfo(name: 'nick_name') @@ -25,7 +31,7 @@ class Dog { @ColumnInfo(name: 'owner_id') final int ownerId; - Dog(this.id, this.name, this.nickName, this.ownerId); + Dog(this.id,this.color ,this.alias, this.name, this.nickName, this.ownerId); @override bool operator ==(Object other) => @@ -33,16 +39,18 @@ class Dog { other is Dog && runtimeType == other.runtimeType && id == other.id && + color == other.color && + alias == other.alias && name == other.name && nickName == other.nickName && ownerId == other.ownerId; @override int get hashCode => - id.hashCode ^ name.hashCode ^ nickName.hashCode ^ ownerId.hashCode; + id.hashCode ^ color.hashCode ^ alias.hashCode ^ name.hashCode ^ nickName.hashCode ^ ownerId.hashCode; @override String toString() { - return 'Dog{id: $id, name: $name, nickName: $nickName, ownerId: $ownerId}'; + return 'Dog{id: $id, color: $color, alias: $alias, name: $name, nickName: $nickName, ownerId: $ownerId}'; } } diff --git a/floor_test/test/model/person.dart b/floor_test/test/model/person.dart index 817f9aa9..03e9841c 100644 --- a/floor_test/test/model/person.dart +++ b/floor_test/test/model/person.dart @@ -10,10 +10,17 @@ class Person { @primaryKey final int id; + @transient + final String nickName; + + @transient + final String alias; + + @ColumnInfo(name: 'custom_name', nullable: false) final String name; - Person(this.id, this.name); + Person(this.id,this.nickName,this.alias, this.name); @override bool operator ==(Object other) => @@ -21,13 +28,15 @@ class Person { other is Person && runtimeType == other.runtimeType && id == other.id && - name == other.name; + nickName == other.nickName && + alias == other.alias && + name == other.name ; @override - int get hashCode => id.hashCode ^ name.hashCode; + int get hashCode => id.hashCode ^ nickName.hashCode ^ alias.hashCode ^ name.hashCode; @override String toString() { - return 'Person{id: $id, name: $name}'; + return 'Person{id: $id, nickName: $nickName, alias: $alias, name: $name}'; } }