diff --git a/floor/pubspec.yaml b/floor/pubspec.yaml index 50f284e7..9f04c86d 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 diff --git a/floor/test/integration/dao/dog_dao.dart b/floor/test/integration/dao/dog_dao.dart index 0be242b4..39622e77 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,10 @@ abstract class DogDao { @Query('SELECT * FROM dog') Future> findAllDogs(); + + @update + Future updateDog(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 1d5b7378..db10150e 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,19 @@ 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 + }), + _dogUpdateAdapter = UpdateAdapter( + database, + 'dog', + ['id'], + (Dog item) => { + 'id': item.id, + 'name': item.name, + 'nick_name': item.nickName, + 'owner_id': item.ownerId, + 'picture': item.picture }); final sqflite.DatabaseExecutor database; @@ -310,10 +322,13 @@ 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'] as Uint8List); final InsertionAdapter _dogInsertionAdapter; + final UpdateAdapter _dogUpdateAdapter; + @override Future findDogForPersonId(int id) async { return _queryAdapter.query('SELECT * FROM dog WHERE owner_id = ?', @@ -325,8 +340,19 @@ 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 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 e2afdc93..3e25b08a 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'; @@ -75,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.insertDog(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', () { @@ -212,7 +227,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 +235,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 +246,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..f3b873c2 100644 --- a/floor/test/integration/model/dog.dart +++ b/floor/test/integration/model/dog.dart @@ -1,3 +1,6 @@ +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; import 'package:floor/floor.dart'; import 'person.dart'; @@ -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 && + const ListEquality().equals(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_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/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 42bff6b9..758babe0 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.isUint8List) { + return '$parameterValue as Uint8List'; } 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..5ec9a04c 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.isUint8List) { + return SqlType.BLOB; } throw InvalidGenerationSourceError( 'Column type is not supported for $type.', diff --git a/floor_generator/test/processor/field_processor_test.dart b/floor_generator/test/processor/field_processor_test.dart index 50e4c1f5..ea7343cb 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