From 061a886dabb85e1b626d3d4ffc8f574dbbaf79b6 Mon Sep 17 00:00:00 2001 From: Markus Richter <8398165+mqus@users.noreply.github.com> Date: Fri, 14 Feb 2020 00:21:09 +0100 Subject: [PATCH 1/4] Feature: Add support for ByteArrays/Blobs This connects the dart type Uint8List to the SQLite data type BLOB and adjusts some tests for that. Fixes #229. --- floor/test/integration/database.g.dart | 8 ++++--- floor/test/integration/database_test.dart | 8 ++++--- floor/test/integration/model/dog.dart | 10 +++++++-- floor/test/test_util/list_helper.dart | 22 +++++++++++++++++++ floor_generator/lib/misc/constants.dart | 1 + .../lib/processor/entity_processor.dart | 2 ++ .../lib/processor/field_processor.dart | 2 ++ 7 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 floor/test/test_util/list_helper.dart diff --git a/floor/test/integration/database.g.dart b/floor/test/integration/database.g.dart index 1d5b7378..26496dc9 100644 --- a/floor/test/integration/database.g.dart +++ b/floor/test/integration/database.g.dart @@ -85,7 +85,7 @@ class _$TestDatabase extends TestDatabase { await database.execute( 'CREATE TABLE IF NOT EXISTS `person` (`id` INTEGER, `custom_name` TEXT NOT NULL, PRIMARY KEY (`id`))'); await database.execute( - 'CREATE TABLE IF NOT EXISTS `dog` (`id` INTEGER, `name` TEXT, `nick_name` TEXT, `owner_id` INTEGER, FOREIGN KEY (`owner_id`) REFERENCES `person` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE, PRIMARY KEY (`id`))'); + 'CREATE TABLE IF NOT EXISTS `dog` (`id` INTEGER, `name` TEXT, `nick_name` TEXT, `owner_id` INTEGER, `picture` BLOB, FOREIGN KEY (`owner_id`) REFERENCES `person` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE, PRIMARY KEY (`id`))'); await database.execute( 'CREATE INDEX `index_person_custom_name` ON `person` (`custom_name`)'); @@ -297,7 +297,8 @@ class _$DogDao extends DogDao { 'id': item.id, 'name': item.name, 'nick_name': item.nickName, - 'owner_id': item.ownerId + 'owner_id': item.ownerId, + 'picture': item.picture }); final sqflite.DatabaseExecutor database; @@ -310,7 +311,8 @@ class _$DogDao extends DogDao { row['id'] as int, row['name'] as String, row['nick_name'] as String, - row['owner_id'] as int); + row['owner_id'] as int, + row['picture']); final InsertionAdapter _dogInsertionAdapter; diff --git a/floor/test/integration/database_test.dart b/floor/test/integration/database_test.dart index e2afdc93..ab31db8b 100644 --- a/floor/test/integration/database_test.dart +++ b/floor/test/integration/database_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:floor/floor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:matcher/matcher.dart'; @@ -212,7 +214,7 @@ void main() { group('foreign key', () { test('foreign key constraint failed exception', () { - final dog = Dog(null, 'Peter', 'Pete', 2); + final dog = Dog(null, 'Peter', 'Pete', 2, Uint8List(8)); expect(() => dogDao.insertDog(dog), _throwsDatabaseException); }); @@ -220,7 +222,7 @@ void main() { test('find dog for person', () async { final person = Person(1, 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2, 'Peter', 'Pete', person.id, Uint8List(8)); await dogDao.insertDog(dog); final actual = await dogDao.findDogForPersonId(person.id); @@ -231,7 +233,7 @@ void main() { test('cascade delete dog on deletion of person', () async { final person = Person(1, 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2, 'Peter', 'Pete', person.id, Uint8List(8)); await dogDao.insertDog(dog); await personDao.deletePerson(person); diff --git a/floor/test/integration/model/dog.dart b/floor/test/integration/model/dog.dart index 35066a39..f2e3643f 100644 --- a/floor/test/integration/model/dog.dart +++ b/floor/test/integration/model/dog.dart @@ -1,5 +1,8 @@ +import 'dart:typed_data'; + import 'package:floor/floor.dart'; +import '../../test_util/list_helper.dart'; import 'person.dart'; @Entity( @@ -25,7 +28,9 @@ class Dog { @ColumnInfo(name: 'owner_id') final int ownerId; - Dog(this.id, this.name, this.nickName, this.ownerId); + final Uint8List picture; + + Dog(this.id, this.name, this.nickName, this.ownerId, this.picture); @override bool operator ==(Object other) => @@ -35,6 +40,7 @@ class Dog { id == other.id && name == other.name && nickName == other.nickName && + ListHelper.deepEquals(picture, other.picture) && ownerId == other.ownerId; @override @@ -43,6 +49,6 @@ class Dog { @override String toString() { - return 'Dog{id: $id, name: $name, nickName: $nickName, ownerId: $ownerId}'; + return 'Dog{id: $id, name: $name, nickName: $nickName, ownerId: $ownerId, picture: $picture}'; } } diff --git a/floor/test/test_util/list_helper.dart b/floor/test/test_util/list_helper.dart new file mode 100644 index 00000000..ded6f79b --- /dev/null +++ b/floor/test/test_util/list_helper.dart @@ -0,0 +1,22 @@ +import 'dart:typed_data'; + +class ListHelper{ + static bool deepEquals(final Uint8List l1,final Uint8List l2){ + if(identical(l1,l2)){ + return true; + } + if(l1 == null || l2 == null){ + return false; + } + if(l1.length!=l2.length) { + return false; + } + for(int i=0; i < l1.length ; ++i) { + if (l1.elementAt(i) != l2.elementAt(i)) { + return false; + } + } + return true; + } +} + diff --git a/floor_generator/lib/misc/constants.dart b/floor_generator/lib/misc/constants.dart index 9654fd51..e84b513b 100644 --- a/floor_generator/lib/misc/constants.dart +++ b/floor_generator/lib/misc/constants.dart @@ -33,6 +33,7 @@ abstract class SqlType { static const INTEGER = 'INTEGER'; static const TEXT = 'TEXT'; static const REAL = 'REAL'; + static const BLOB = 'BLOB'; } abstract class OnConflictStrategy { diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index 42bff6b9..6bf74d2f 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -266,6 +266,8 @@ class EntityProcessor extends Processor { return '$parameterValue as String'; } else if (parameterType.isDartCoreInt) { return '$parameterValue as int'; + } else if (parameterType.getDisplayString() == 'Uint8List') { + return '$parameterValue'; } else { return '$parameterValue as double'; // must be double } diff --git a/floor_generator/lib/processor/field_processor.dart b/floor_generator/lib/processor/field_processor.dart index 946b34d5..29687b3c 100644 --- a/floor_generator/lib/processor/field_processor.dart +++ b/floor_generator/lib/processor/field_processor.dart @@ -66,6 +66,8 @@ class FieldProcessor extends Processor { return SqlType.INTEGER; } else if (type.isDartCoreDouble) { return SqlType.REAL; + } else if (type.getDisplayString() == 'Uint8List') { + return SqlType.BLOB; } throw InvalidGenerationSourceError( 'Column type is not supported for $type.', From 54710ae804010a7b14f58408c5a2319e37397a41 Mon Sep 17 00:00:00 2001 From: Markus Richter <8398165+mqus@users.noreply.github.com> Date: Sat, 15 Feb 2020 23:24:56 +0100 Subject: [PATCH 2/4] add more tests --- floor/pubspec.yaml | 3 +- floor/test/integration/dao/dog_dao.dart | 11 +++++++ floor/test/integration/database.dart | 1 + floor/test/integration/database.g.dart | 29 +++++++++++++++++++ floor/test/integration/database_test.dart | 13 +++++++++ floor/test/integration/model/dog.dart | 4 +-- floor/test/test_util/list_helper.dart | 22 -------------- .../test/processor/field_processor_test.dart | 23 +++++++++++++++ 8 files changed, 81 insertions(+), 25 deletions(-) delete mode 100644 floor/test/test_util/list_helper.dart diff --git a/floor/pubspec.yaml b/floor/pubspec.yaml index 50f284e7..6d59905a 100644 --- a/floor/pubspec.yaml +++ b/floor/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: sdk: flutter dev_dependencies: + collection: ^1.14.11 mockito: ^4.1.1 flutter_test: sdk: flutter @@ -28,4 +29,4 @@ dev_dependencies: git: url: git://github.com/tekartik/sqflite_more ref: dart2 - path: sqflite_ffi_test + path: sqflite_ffi_test \ No newline at end of file diff --git a/floor/test/integration/dao/dog_dao.dart b/floor/test/integration/dao/dog_dao.dart index 0be242b4..d88ba54b 100644 --- a/floor/test/integration/dao/dog_dao.dart +++ b/floor/test/integration/dao/dog_dao.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:floor/floor.dart'; import '../model/dog.dart'; @@ -12,4 +14,13 @@ abstract class DogDao { @Query('SELECT * FROM dog') Future> findAllDogs(); + + @update + Future updateDog(Dog dog); + + @insert + Future addDog(Dog dog); + + @Query('SELECT * FROM dog WHERE picture = :pic') + Future findDogForPicture(Uint8List pic); } diff --git a/floor/test/integration/database.dart b/floor/test/integration/database.dart index 5efe1988..eb1f7d38 100644 --- a/floor/test/integration/database.dart +++ b/floor/test/integration/database.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:floor/floor.dart'; import 'package:path/path.dart'; diff --git a/floor/test/integration/database.g.dart b/floor/test/integration/database.g.dart index 26496dc9..232f5611 100644 --- a/floor/test/integration/database.g.dart +++ b/floor/test/integration/database.g.dart @@ -293,6 +293,17 @@ class _$DogDao extends DogDao { _dogInsertionAdapter = InsertionAdapter( database, 'dog', + (Dog item) => { + 'id': item.id, + 'name': item.name, + 'nick_name': item.nickName, + 'owner_id': item.ownerId, + 'picture': item.picture + }), + _dogUpdateAdapter = UpdateAdapter( + database, + 'dog', + ['id'], (Dog item) => { 'id': item.id, 'name': item.name, @@ -316,6 +327,8 @@ class _$DogDao extends DogDao { final InsertionAdapter _dogInsertionAdapter; + final UpdateAdapter _dogUpdateAdapter; + @override Future findDogForPersonId(int id) async { return _queryAdapter.query('SELECT * FROM dog WHERE owner_id = ?', @@ -327,8 +340,24 @@ class _$DogDao extends DogDao { return _queryAdapter.queryList('SELECT * FROM dog', mapper: _dogMapper); } + @override + Future findDogForPicture(Uint8List pic) async { + return _queryAdapter.query('SELECT * FROM dog WHERE picture = ?', + arguments: [pic], mapper: _dogMapper); + } + @override Future insertDog(Dog dog) async { await _dogInsertionAdapter.insert(dog, sqflite.ConflictAlgorithm.abort); } + + @override + Future addDog(Dog dog) async { + await _dogInsertionAdapter.insert(dog, sqflite.ConflictAlgorithm.abort); + } + + @override + Future updateDog(Dog dog) async { + await _dogUpdateAdapter.update(dog, sqflite.ConflictAlgorithm.abort); + } } diff --git a/floor/test/integration/database_test.dart b/floor/test/integration/database_test.dart index ab31db8b..744c11e2 100644 --- a/floor/test/integration/database_test.dart +++ b/floor/test/integration/database_test.dart @@ -77,6 +77,19 @@ void main() { final actual = await personDao.findPersonById(person.id); expect(actual, equals(updatedPerson)); }); + + test('insert/update dog, search by Uint8List', () async { + final person = Person(1, 'Simon'); + await personDao.insertPerson(person); + final dog = Dog(1, 'Dogbert', 'Doggy', 1, Uint8List(9)); + await dogDao.addDog(dog); + final updatedDog = Dog(1, 'Dogbert 2.', 'Doggy', 1, Uint8List(7)); + + await dogDao.updateDog(updatedDog); + + final actual = await dogDao.findDogForPicture(Uint8List(7)); + expect(actual, equals(updatedDog)); + }); }); group('change multiple items', () { diff --git a/floor/test/integration/model/dog.dart b/floor/test/integration/model/dog.dart index f2e3643f..f3b873c2 100644 --- a/floor/test/integration/model/dog.dart +++ b/floor/test/integration/model/dog.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; +import 'package:collection/collection.dart'; import 'package:floor/floor.dart'; -import '../../test_util/list_helper.dart'; import 'person.dart'; @Entity( @@ -40,7 +40,7 @@ class Dog { id == other.id && name == other.name && nickName == other.nickName && - ListHelper.deepEquals(picture, other.picture) && + const ListEquality().equals(picture, other.picture) && ownerId == other.ownerId; @override diff --git a/floor/test/test_util/list_helper.dart b/floor/test/test_util/list_helper.dart deleted file mode 100644 index ded6f79b..00000000 --- a/floor/test/test_util/list_helper.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:typed_data'; - -class ListHelper{ - static bool deepEquals(final Uint8List l1,final Uint8List l2){ - if(identical(l1,l2)){ - return true; - } - if(l1 == null || l2 == null){ - return false; - } - if(l1.length!=l2.length) { - return false; - } - for(int i=0; i < l1.length ; ++i) { - if (l1.elementAt(i) != l2.elementAt(i)) { - return false; - } - } - return true; - } -} - diff --git a/floor_generator/test/processor/field_processor_test.dart b/floor_generator/test/processor/field_processor_test.dart index 50e4c1f5..48f8d1e8 100644 --- a/floor_generator/test/processor/field_processor_test.dart +++ b/floor_generator/test/processor/field_processor_test.dart @@ -27,6 +27,28 @@ void main() { ); expect(actual, equals(expected)); }); + + test('Process Uint8List field', () async { + final fieldElement = await _generateFieldElement(''' + @ColumnInfo(name : 'data', nullable:false) + final Uint8List bytes; + '''); + + final actual = FieldProcessor(fieldElement).process(); + + const name = 'bytes'; + const columnName = 'data'; + const isNullable = false; + const sqlType = SqlType.BLOB; + final expected = Field( + fieldElement, + name, + columnName, + isNullable, + sqlType, + ); + expect(actual, equals(expected)); + }); } Future _generateFieldElement(final String field) async { @@ -34,6 +56,7 @@ Future _generateFieldElement(final String field) async { library test; import 'package:floor_annotation/floor_annotation.dart'; + import 'dart:typed_data'; class Foo { $field From 598a6a59e83c3214695cee54fccaa707039f1c64 Mon Sep 17 00:00:00 2001 From: Markus Richter <8398165+mqus@users.noreply.github.com> Date: Wed, 19 Feb 2020 22:08:43 +0100 Subject: [PATCH 3/4] apply suggestions from review --- floor/pubspec.yaml | 2 +- floor_generator/lib/misc/type_utils.dart | 5 +++++ floor_generator/lib/processor/entity_processor.dart | 2 +- floor_generator/lib/processor/field_processor.dart | 2 +- floor_generator/test/processor/field_processor_test.dart | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/floor/pubspec.yaml b/floor/pubspec.yaml index 6d59905a..9f04c86d 100644 --- a/floor/pubspec.yaml +++ b/floor/pubspec.yaml @@ -29,4 +29,4 @@ dev_dependencies: git: url: git://github.com/tekartik/sqflite_more ref: dart2 - path: sqflite_ffi_test \ No newline at end of file + path: sqflite_ffi_test diff --git a/floor_generator/lib/misc/type_utils.dart b/floor_generator/lib/misc/type_utils.dart index 17616ec5..fff8e967 100644 --- a/floor_generator/lib/misc/type_utils.dart +++ b/floor_generator/lib/misc/type_utils.dart @@ -16,6 +16,11 @@ extension SupportedTypeChecker on DartType { } } +extension Uint8ListTypeChecker on DartType { + @nonNull + bool get isUint8List => getDisplayString() == 'Uint8List'; +} + extension StreamTypeChecker on DartType { @nonNull bool get isStream => _streamTypeChecker.isExactlyType(this); diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index 6bf74d2f..6e632cbc 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -266,7 +266,7 @@ class EntityProcessor extends Processor { return '$parameterValue as String'; } else if (parameterType.isDartCoreInt) { return '$parameterValue as int'; - } else if (parameterType.getDisplayString() == 'Uint8List') { + } else if (parameterType.isUint8List) { return '$parameterValue'; } else { return '$parameterValue as double'; // must be double diff --git a/floor_generator/lib/processor/field_processor.dart b/floor_generator/lib/processor/field_processor.dart index 29687b3c..5ec9a04c 100644 --- a/floor_generator/lib/processor/field_processor.dart +++ b/floor_generator/lib/processor/field_processor.dart @@ -66,7 +66,7 @@ class FieldProcessor extends Processor { return SqlType.INTEGER; } else if (type.isDartCoreDouble) { return SqlType.REAL; - } else if (type.getDisplayString() == 'Uint8List') { + } else if (type.isUint8List) { return SqlType.BLOB; } throw InvalidGenerationSourceError( diff --git a/floor_generator/test/processor/field_processor_test.dart b/floor_generator/test/processor/field_processor_test.dart index 48f8d1e8..ea7343cb 100644 --- a/floor_generator/test/processor/field_processor_test.dart +++ b/floor_generator/test/processor/field_processor_test.dart @@ -30,7 +30,7 @@ void main() { test('Process Uint8List field', () async { final fieldElement = await _generateFieldElement(''' - @ColumnInfo(name : 'data', nullable:false) + @ColumnInfo(name: 'data', nullable: false) final Uint8List bytes; '''); From f521d3d8007e7b1d443ee85c9db177e54bcefa36 Mon Sep 17 00:00:00 2001 From: Markus Richter <8398165+mqus@users.noreply.github.com> Date: Sat, 22 Feb 2020 08:34:58 +0100 Subject: [PATCH 4/4] add suggested fixes --- floor/test/integration/dao/dog_dao.dart | 3 --- floor/test/integration/database.g.dart | 7 +------ floor/test/integration/database_test.dart | 4 ++-- floor_generator/lib/processor/entity_processor.dart | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/floor/test/integration/dao/dog_dao.dart b/floor/test/integration/dao/dog_dao.dart index d88ba54b..39622e77 100644 --- a/floor/test/integration/dao/dog_dao.dart +++ b/floor/test/integration/dao/dog_dao.dart @@ -18,9 +18,6 @@ abstract class DogDao { @update Future updateDog(Dog dog); - @insert - Future addDog(Dog dog); - @Query('SELECT * FROM dog WHERE picture = :pic') Future findDogForPicture(Uint8List pic); } diff --git a/floor/test/integration/database.g.dart b/floor/test/integration/database.g.dart index 232f5611..db10150e 100644 --- a/floor/test/integration/database.g.dart +++ b/floor/test/integration/database.g.dart @@ -323,7 +323,7 @@ class _$DogDao extends DogDao { row['name'] as String, row['nick_name'] as String, row['owner_id'] as int, - row['picture']); + row['picture'] as Uint8List); final InsertionAdapter _dogInsertionAdapter; @@ -351,11 +351,6 @@ class _$DogDao extends DogDao { await _dogInsertionAdapter.insert(dog, sqflite.ConflictAlgorithm.abort); } - @override - Future addDog(Dog dog) async { - await _dogInsertionAdapter.insert(dog, sqflite.ConflictAlgorithm.abort); - } - @override Future updateDog(Dog dog) async { await _dogUpdateAdapter.update(dog, sqflite.ConflictAlgorithm.abort); diff --git a/floor/test/integration/database_test.dart b/floor/test/integration/database_test.dart index 744c11e2..3e25b08a 100644 --- a/floor/test/integration/database_test.dart +++ b/floor/test/integration/database_test.dart @@ -82,9 +82,9 @@ void main() { final person = Person(1, 'Simon'); await personDao.insertPerson(person); final dog = Dog(1, 'Dogbert', 'Doggy', 1, Uint8List(9)); - await dogDao.addDog(dog); - final updatedDog = Dog(1, 'Dogbert 2.', 'Doggy', 1, Uint8List(7)); + await dogDao.insertDog(dog); + final updatedDog = Dog(1, 'Dogbert 2.', 'Doggy', 1, Uint8List(7)); await dogDao.updateDog(updatedDog); final actual = await dogDao.findDogForPicture(Uint8List(7)); diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index 6e632cbc..758babe0 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -267,7 +267,7 @@ class EntityProcessor extends Processor { } else if (parameterType.isDartCoreInt) { return '$parameterValue as int'; } else if (parameterType.isUint8List) { - return '$parameterValue'; + return '$parameterValue as Uint8List'; } else { return '$parameterValue as double'; // must be double }