From 07767af1bc85b8530722110b40b2fc58e1585261 Mon Sep 17 00:00:00 2001 From: Vitus Date: Wed, 6 Mar 2019 14:29:29 +0100 Subject: [PATCH] Support accessing data from Data Access Objects (#82) --- floor/lib/src/database.dart | 2 - floor_annotation/lib/floor_annotation.dart | 1 + floor_annotation/lib/src/dao.dart | 15 ++ floor_generator/lib/generator.dart | 6 +- floor_generator/lib/misc/constants.dart | 1 + floor_generator/lib/misc/type_utils.dart | 4 + floor_generator/lib/model/dao.dart | 79 +++++++++ floor_generator/lib/model/database.dart | 87 +++++----- .../lib/model/transaction_method.dart | 7 +- floor_generator/lib/writer/dao_writer.dart | 149 ++++++++++++++++ .../lib/writer/database_writer.dart | 162 ++++++------------ .../lib/writer/transaction_method_writer.dart | 2 +- .../test/insert_method_writer_test.dart | 16 +- floor_test/test/dao/dog_dao.dart | 13 ++ floor_test/test/dao/person_dao.dart | 64 +++++++ floor_test/test/database.dart | 76 +------- floor_test/test/database_test.dart | 132 +++++++------- 17 files changed, 513 insertions(+), 303 deletions(-) create mode 100644 floor_annotation/lib/src/dao.dart create mode 100644 floor_generator/lib/model/dao.dart create mode 100644 floor_generator/lib/writer/dao_writer.dart create mode 100644 floor_test/test/dao/dog_dao.dart create mode 100644 floor_test/test/dao/person_dao.dart diff --git a/floor/lib/src/database.dart b/floor/lib/src/database.dart index ae2d7410..188ca461 100644 --- a/floor/lib/src/database.dart +++ b/floor/lib/src/database.dart @@ -10,10 +10,8 @@ abstract class FloorDatabase { final changeListener = StreamController.broadcast(); /// Use this whenever you want need direct access to the sqflite database. - @protected sqflite.DatabaseExecutor database; - // TODO remove this /// Opens the database to be able to query it. Future open(List migrations); diff --git a/floor_annotation/lib/floor_annotation.dart b/floor_annotation/lib/floor_annotation.dart index 995c7304..a63e4606 100644 --- a/floor_annotation/lib/floor_annotation.dart +++ b/floor_annotation/lib/floor_annotation.dart @@ -1,6 +1,7 @@ library floor_annotation; export 'src/column_info.dart'; +export 'src/dao.dart'; export 'src/database.dart'; export 'src/delete.dart'; export 'src/entity.dart'; diff --git a/floor_annotation/lib/src/dao.dart b/floor_annotation/lib/src/dao.dart new file mode 100644 index 00000000..0bba5943 --- /dev/null +++ b/floor_annotation/lib/src/dao.dart @@ -0,0 +1,15 @@ +class _Dao { + const _Dao(); +} + +/// Marks the class as a Data Access Object. +/// +/// Data Access Objects are the main classes where you define your database +/// interactions. They can include a variety of query methods. +/// The class marked with @dao should either be an abstract class. At compile +/// time, Floor will generate an implementation of this class when it is +/// referenced by a Database. +/// +/// It is recommended to have multiple Dao classes in your codebase depending +/// on the tables they touch. +const dao = _Dao(); diff --git a/floor_generator/lib/generator.dart b/floor_generator/lib/generator.dart index 1e7bb505..1c2a5d73 100644 --- a/floor_generator/lib/generator.dart +++ b/floor_generator/lib/generator.dart @@ -13,11 +13,7 @@ class FloorGenerator implements Generator { final BuildStep buildStep, ) { final database = DatabaseWriter(library).write(); - - // TODO generator runs for every file of the project, so this fails without - if (database == null) { - return null; - } + if (database == null) return null; return database.accept(DartEmitter()).toString(); } diff --git a/floor_generator/lib/misc/constants.dart b/floor_generator/lib/misc/constants.dart index ff29173b..0f972bfb 100644 --- a/floor_generator/lib/misc/constants.dart +++ b/floor_generator/lib/misc/constants.dart @@ -11,6 +11,7 @@ abstract class Annotation { static const COLUMN_INFO = 'ColumnInfo'; static const PRIMARY_KEY = 'PrimaryKey'; static const TRANSACTION = '_Transaction'; + static const DAO = '_Dao'; static const QUERY = 'Query'; static const INSERT = 'Insert'; diff --git a/floor_generator/lib/misc/type_utils.dart b/floor_generator/lib/misc/type_utils.dart index f82a8e20..98633598 100644 --- a/floor_generator/lib/misc/type_utils.dart +++ b/floor_generator/lib/misc/type_utils.dart @@ -72,6 +72,10 @@ bool isTransactionAnnotation(final ElementAnnotation annotation) { return _getAnnotationName(annotation) == Annotation.TRANSACTION; } +bool isDaoAnnotation(final ElementAnnotation annotation) { + return _getAnnotationName(annotation) == Annotation.DAO; +} + DartType flattenList(final DartType type) { return (type as ParameterizedType).typeArguments.first; } diff --git a/floor_generator/lib/model/dao.dart b/floor_generator/lib/model/dao.dart new file mode 100644 index 00000000..ef3b00fd --- /dev/null +++ b/floor_generator/lib/model/dao.dart @@ -0,0 +1,79 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/misc/type_utils.dart'; +import 'package:floor_generator/model/delete_method.dart'; +import 'package:floor_generator/model/entity.dart'; +import 'package:floor_generator/model/insert_method.dart'; +import 'package:floor_generator/model/query_method.dart'; +import 'package:floor_generator/model/transaction_method.dart'; +import 'package:floor_generator/model/update_method.dart'; +import 'package:source_gen/source_gen.dart'; + +class Dao { + final ClassElement clazz; + final String daoFieldName; + final String databaseName; + + Dao(final this.clazz, final this.daoFieldName, final this.databaseName); + + String get name => _nameCache ??= clazz.displayName; + + String _nameCache; + + List get methods => _methodsCache ??= clazz.methods; + + List _methodsCache; + + List get queryMethods { + return _queryMethodsCache ??= methods + .where((method) => method.metadata.any(isQueryAnnotation)) + .map((method) => QueryMethod(method)) + .toList(); + } + + List _queryMethodsCache; + + List get insertMethods { + return _insertMethodCache ??= methods + .where((method) => method.metadata.any(isInsertAnnotation)) + .map((method) => InsertMethod(method)) + .toList(); + } + + List _insertMethodCache; + + List get updateMethods { + return _updateMethodCache ??= methods + .where((method) => method.metadata.any(isUpdateAnnotation)) + .map((method) => UpdateMethod(method)) + .toList(); + } + + List _updateMethodCache; + + List get deleteMethods { + return _deleteMethodCache ??= methods + .where((method) => method.metadata.any(isDeleteAnnotation)) + .map((method) => DeleteMethod(method)) + .toList(); + } + + List _deleteMethodCache; + + List get transactionMethods { + return _transactionMethodCache ??= methods + .where((method) => method.metadata.any(isTransactionAnnotation)) + .map((method) => TransactionMethod(method, daoFieldName, databaseName)) + .toList(); + } + + List _transactionMethodCache; + + List getStreamEntities(final LibraryReader library) { + return _streamEntitiesCache ??= queryMethods + .where((method) => method.returnsStream) + .map((method) => method.getEntity(library)) + .toList(); + } + + List _streamEntitiesCache; +} diff --git a/floor_generator/lib/model/database.dart b/floor_generator/lib/model/database.dart index f25abfae..3109df57 100644 --- a/floor_generator/lib/model/database.dart +++ b/floor_generator/lib/model/database.dart @@ -1,12 +1,9 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/type_utils.dart'; -import 'package:floor_generator/model/delete_method.dart'; +import 'package:floor_generator/model/dao.dart'; import 'package:floor_generator/model/entity.dart'; -import 'package:floor_generator/model/insert_method.dart'; import 'package:floor_generator/model/query_method.dart'; -import 'package:floor_generator/model/transaction_method.dart'; -import 'package:floor_generator/model/update_method.dart'; import 'package:source_gen/source_gen.dart'; class Database { @@ -14,16 +11,22 @@ class Database { Database(final this.clazz); - String get name => clazz.displayName; + String _nameCache; + + String get name => _nameCache ??= clazz.displayName; + + int _versionCache; int get version { + if (_versionCache != null) return _versionCache; + final databaseVersion = clazz.metadata .firstWhere(isDatabaseAnnotation) .computeConstantValue() .getField(AnnotationField.DATABASE_VERSION) ?.toIntValue(); - return databaseVersion != null + return _versionCache ??= databaseVersion != null ? databaseVersion : throw InvalidGenerationSourceError( 'No version for this database specified even though it is required.', @@ -31,59 +34,65 @@ class Database { ); } - List get methods => clazz.methods; + List _methodsCache; + + List get methods => _methodsCache ??= clazz.methods; + + List getEntities(final LibraryReader library) { + return library.classes + .where((clazz) => + !clazz.isAbstract && clazz.metadata.any(isEntityAnnotation)) + .map((entity) => Entity(entity)) + .toList(); + } List _queryMethodsCache; - List get queryMethods { + List get _queryMethods { return _queryMethodsCache ??= methods .where((method) => method.metadata.any(isQueryAnnotation)) .map((method) => QueryMethod(method)) .toList(); } - List get insertMethods { - return methods - .where((method) => method.metadata.any(isInsertAnnotation)) - .map((method) => InsertMethod(method)) - .toList(); - } + List _streamEntities; - List get updateMethods { - return methods - .where((method) => method.metadata.any(isUpdateAnnotation)) - .map((method) => UpdateMethod(method)) + List getStreamEntities(final LibraryReader library) { + return _streamEntities ??= _queryMethods + .where((method) => method.returnsStream) + .map((method) => method.getEntity(library)) .toList(); } - List get deleteMethods { - return methods - .where((method) => method.metadata.any(isDeleteAnnotation)) - .map((method) => DeleteMethod(method)) + List _daosCache; + + List getDaos(final LibraryReader library) { + return _daosCache ??= library.classes + .where(_isDaoClass) + .where(_isDefinedInDatabase) + .map((daoClass) => Dao(daoClass, _getDaoFieldName(daoClass), name)) .toList(); } - List get transactionMethods { - return methods - .where((method) => method.metadata.any(isTransactionAnnotation)) - .map((method) => TransactionMethod(method, name)) - .toList(); + String _getDaoFieldName(final ClassElement daoClass) { + return clazz.fields + .firstWhere((field) => field.type.displayName == daoClass.displayName) + .displayName; } - List getEntities(final LibraryReader library) { - return library.classes - .where((clazz) => - !clazz.isAbstract && clazz.metadata.any(isEntityAnnotation)) - .map((entity) => Entity(entity)) - .toList(); + bool _isDaoClass(final ClassElement clazz) { + return clazz.metadata.any(isDaoAnnotation) && clazz.isAbstract; } - List _streamEntities; + List _fieldTypeNamesCache; - List getStreamEntities(final LibraryReader library) { - return _streamEntities ??= queryMethods - .where((method) => method.returnsStream) - .map((method) => method.getEntity(library)) - .toList(); + List get _fieldTypeNames { + return _fieldTypeNamesCache ??= + clazz.fields.map((field) => field.type.displayName).toList(); + } + + bool _isDefinedInDatabase(final ClassElement daoClass) { + return _fieldTypeNames + .any((fieldType) => daoClass.displayName == fieldType); } } diff --git a/floor_generator/lib/model/transaction_method.dart b/floor_generator/lib/model/transaction_method.dart index 2417e992..8f26680b 100644 --- a/floor_generator/lib/model/transaction_method.dart +++ b/floor_generator/lib/model/transaction_method.dart @@ -3,9 +3,14 @@ import 'package:analyzer/dart/element/type.dart'; class TransactionMethod { final MethodElement method; + final String daoFieldName; final String databaseName; - TransactionMethod(final this.method, final this.databaseName); + TransactionMethod( + final this.method, + final this.daoFieldName, + final this.databaseName, + ); DartType get returnType => method.returnType; diff --git a/floor_generator/lib/writer/dao_writer.dart b/floor_generator/lib/writer/dao_writer.dart new file mode 100644 index 00000000..b4154387 --- /dev/null +++ b/floor_generator/lib/writer/dao_writer.dart @@ -0,0 +1,149 @@ +import 'package:code_builder/code_builder.dart'; +import 'package:floor_generator/model/dao.dart'; +import 'package:floor_generator/model/delete_method.dart'; +import 'package:floor_generator/model/insert_method.dart'; +import 'package:floor_generator/model/query_method.dart'; +import 'package:floor_generator/model/transaction_method.dart'; +import 'package:floor_generator/model/update_method.dart'; +import 'package:floor_generator/writer/adapter/deletion_adapters_writer.dart'; +import 'package:floor_generator/writer/adapter/insertion_adapters_writer.dart'; +import 'package:floor_generator/writer/adapter/query_adapter_writer.dart'; +import 'package:floor_generator/writer/adapter/update_adapters_writer.dart'; +import 'package:floor_generator/writer/change_method_writer.dart'; +import 'package:floor_generator/writer/delete_method_body_writer.dart'; +import 'package:floor_generator/writer/insert_method_body_writer.dart'; +import 'package:floor_generator/writer/query_method_writer.dart'; +import 'package:floor_generator/writer/transaction_method_writer.dart'; +import 'package:floor_generator/writer/update_method_body_writer.dart'; +import 'package:floor_generator/writer/writer.dart'; +import 'package:source_gen/source_gen.dart'; + +class DaoWriter extends Writer { + final LibraryReader library; + final Dao dao; + + DaoWriter(this.library, this.dao); + + @override + Class write() { + const databaseFieldName = 'database'; + const changeListenerFieldName = 'changeListener'; + + final daoName = dao.name; + final builder = ClassBuilder() + ..name = '_\$$daoName' + ..extend = refer(daoName) + ..constructors + .add(_createConstructor(databaseFieldName, changeListenerFieldName)) + ..fields + .addAll(_createFields(databaseFieldName, changeListenerFieldName)); + + final streamEntities = dao.getStreamEntities(library); + + final queryMethods = dao.queryMethods; + if (queryMethods.isNotEmpty) { + QueryAdapterWriter( + library, + builder, + queryMethods, + streamEntities.isNotEmpty, + ).write(); + } + + final insertMethods = dao.insertMethods; + if (insertMethods.isNotEmpty) { + InsertionAdaptersWriter(library, builder, insertMethods, streamEntities) + .write(); + } + + final updateMethods = dao.updateMethods; + if (updateMethods.isNotEmpty) { + UpdateAdaptersWriter(library, builder, updateMethods, streamEntities) + .write(); + } + + final deleteMethods = dao.deleteMethods; + if (deleteMethods.isNotEmpty) { + DeletionAdaptersWriter(library, builder, deleteMethods, streamEntities) + .write(); + } + + builder + ..methods.addAll(_generateQueryMethods(queryMethods)) + ..methods.addAll(_generateInsertMethods(insertMethods)) + ..methods.addAll(_generateUpdateMethods(updateMethods)) + ..methods.addAll(_generateDeleteMethods(deleteMethods)) + ..methods.addAll(_generateTransactionMethods(dao.transactionMethods)); + + return builder.build(); + } + + Constructor _createConstructor( + final String databaseName, + final String changeListenerName, + ) { + final databaseParameter = Parameter((builder) => builder + ..name = databaseName + ..toThis = true); + + final changeListenerParameter = Parameter((builder) => builder + ..name = changeListenerName + ..toThis = true); + + return Constructor((builder) => builder + ..requiredParameters + .addAll([databaseParameter, changeListenerParameter])); + } + + List _createFields( + final String databaseName, + final String changeListenerName, + ) { + final databaseField = Field((builder) => builder + ..name = databaseName + ..type = refer('sqflite.DatabaseExecutor') + ..modifier = FieldModifier.final$); + + final changeListenerField = Field((builder) => builder + ..name = changeListenerName + ..type = refer('StreamController') + ..modifier = FieldModifier.final$); + + return [databaseField, changeListenerField]; + } + + List _generateInsertMethods(final List insertMethods) { + return insertMethods.map((method) { + final writer = InsertMethodBodyWriter(library, method); + return ChangeMethodWriter(library, method, writer).write(); + }).toList(); + } + + List _generateUpdateMethods(final List updateMethods) { + return updateMethods.map((method) { + final writer = UpdateMethodBodyWriter(library, method); + return ChangeMethodWriter(library, method, writer).write(); + }).toList(); + } + + List _generateDeleteMethods(final List deleteMethods) { + return deleteMethods.map((method) { + final writer = DeleteMethodBodyWriter(library, method); + return ChangeMethodWriter(library, method, writer).write(); + }).toList(); + } + + List _generateQueryMethods(final List queryMethods) { + return queryMethods + .map((method) => QueryMethodWriter(library, method).write()) + .toList(); + } + + List _generateTransactionMethods( + final List transactionMethods, + ) { + return transactionMethods + .map((method) => TransactionMethodWriter(library, method).write()) + .toList(); + } +} diff --git a/floor_generator/lib/writer/database_writer.dart b/floor_generator/lib/writer/database_writer.dart index c5bd03f1..4b9b643b 100644 --- a/floor_generator/lib/writer/database_writer.dart +++ b/floor_generator/lib/writer/database_writer.dart @@ -1,23 +1,10 @@ +import 'package:analyzer/dart/element/element.dart'; import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/misc/annotation_expression.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/model/database.dart'; -import 'package:floor_generator/model/delete_method.dart'; import 'package:floor_generator/model/entity.dart'; -import 'package:floor_generator/model/insert_method.dart'; -import 'package:floor_generator/model/query_method.dart'; -import 'package:floor_generator/model/transaction_method.dart'; -import 'package:floor_generator/model/update_method.dart'; -import 'package:floor_generator/writer/adapter/insertion_adapters_writer.dart'; -import 'package:floor_generator/writer/adapter/query_adapter_writer.dart'; -import 'package:floor_generator/writer/adapter/update_adapters_writer.dart'; -import 'package:floor_generator/writer/change_method_writer.dart'; -import 'package:floor_generator/writer/delete_method_body_writer.dart'; -import 'package:floor_generator/writer/adapter/deletion_adapters_writer.dart'; -import 'package:floor_generator/writer/insert_method_body_writer.dart'; -import 'package:floor_generator/writer/query_method_writer.dart'; -import 'package:floor_generator/writer/transaction_method_writer.dart'; -import 'package:floor_generator/writer/update_method_body_writer.dart'; +import 'package:floor_generator/writer/dao_writer.dart'; import 'package:floor_generator/writer/writer.dart'; import 'package:source_gen/source_gen.dart'; @@ -30,17 +17,14 @@ class DatabaseWriter implements Writer { @override Spec write() { final database = _getDatabase(); - - // TODO generator runs for every file of the project, so this fails without - if (database == null) { - return null; - } + if (database == null) return null; return Library((builder) => builder ..body.addAll([ _generateOpenDatabaseFunction(database.name), - _generateDatabaseImplementation(database) - ])); + _generateDatabaseImplementation(database), + ]) + ..body.addAll(_generateDaos(database))); } Database _getDatabase() { @@ -48,10 +32,7 @@ class DatabaseWriter implements Writer { clazz.isAbstract && clazz.metadata.any(isDatabaseAnnotation)); if (databaseClasses.isEmpty) { - // TODO generator runs for every file of the project, so this fails without return null; -// throw InvalidGenerationSourceError( -// 'No database defined. Add a @Database annotation to your abstract database class.'); } else if (databaseClasses.length > 1) { throw InvalidGenerationSourceError( 'Only one database is allowed. There are too many classes annotated with @Database.'); @@ -79,6 +60,43 @@ class DatabaseWriter implements Writer { } Class _generateDatabaseImplementation(final Database database) { + final databaseName = database.name; + + return Class((builder) => builder + ..name = '_\$$databaseName' + ..extend = refer(databaseName) + ..methods.add(_generateOpenMethod(database)) + ..methods.addAll(_generateDaoGetters(database)) + ..fields.addAll(_generateDaoInstances(database))); + } + + List _generateDaoGetters(final Database database) { + return database.getDaos(library).map((dao) { + final daoFieldName = dao.daoFieldName; + final daoType = dao.clazz.displayName; + + return Method((builder) => builder + ..annotations.add(overrideAnnotationExpression) + ..type = MethodType.getter + ..returns = refer(daoType) + ..name = daoFieldName + ..body = Code( + 'return _${daoFieldName}Instance ??= _\$$daoType(database, changeListener);')); + }).toList(); + } + + List _generateDaoInstances(final Database database) { + return database.getDaos(library).map((dao) { + final daoFieldName = dao.daoFieldName; + final daoType = dao.clazz.displayName; + + return Field((builder) => builder + ..type = refer(daoType) + ..name = '_${daoFieldName}Instance'); + }).toList(); + } + + Method _generateOpenMethod(final Database database) { final createTableStatements = _generateCreateTableSqlStatements(database.getEntities(library)) .map((statement) => 'await database.execute($statement);') @@ -89,58 +107,6 @@ class DatabaseWriter implements Writer { 'There are no entities defined. Use the @Entity annotation on persistent classes to do so.'); } - final databaseName = database.name; - - final builder = ClassBuilder() - ..name = '_\$$databaseName' - ..extend = refer(databaseName); - - final streamEntities = database.getStreamEntities(library); - - final queryMethods = database.queryMethods; - if (queryMethods.isNotEmpty) { - QueryAdapterWriter( - library, - builder, - queryMethods, - streamEntities.isNotEmpty, - ).write(); - } - - final insertMethods = database.insertMethods; - if (insertMethods.isNotEmpty) { - InsertionAdaptersWriter(library, builder, insertMethods, streamEntities) - .write(); - } - - final updateMethods = database.updateMethods; - if (updateMethods.isNotEmpty) { - UpdateAdaptersWriter(library, builder, updateMethods, streamEntities) - .write(); - } - - final deleteMethods = database.deleteMethods; - if (deleteMethods.isNotEmpty) { - DeletionAdaptersWriter(library, builder, deleteMethods, streamEntities) - .write(); - } - - builder - ..methods.add(_generateOpenMethod(database, createTableStatements)) - ..methods.addAll(_generateQueryMethods(queryMethods)) - ..methods.addAll(_generateInsertMethods(insertMethods)) - ..methods.addAll(_generateUpdateMethods(updateMethods)) - ..methods.addAll(_generateDeleteMethods(deleteMethods)) - ..methods - .addAll(_generateTransactionMethods(database.transactionMethods)); - - return builder.build(); - } - - Method _generateOpenMethod( - final Database database, - final String createTableStatements, - ) { final migrationsParameter = Parameter((builder) => builder ..name = 'migrations' ..type = refer('List')); @@ -170,44 +136,16 @@ class DatabaseWriter implements Writer { ''')); } - List _generateInsertMethods(final List insertMethods) { - return insertMethods.map((method) { - final writer = InsertMethodBodyWriter(library, method); - return ChangeMethodWriter(library, method, writer).write(); - }).toList(); - } - - List _generateUpdateMethods(final List updateMethods) { - return updateMethods.map((method) { - final writer = UpdateMethodBodyWriter(library, method); - return ChangeMethodWriter(library, method, writer).write(); - }).toList(); - } - - List _generateDeleteMethods(final List deleteMethods) { - return deleteMethods.map((method) { - final writer = DeleteMethodBodyWriter(library, method); - return ChangeMethodWriter(library, method, writer).write(); - }).toList(); - } - - List _generateQueryMethods(final List queryMethods) { - return queryMethods - .map((method) => QueryMethodWriter(library, method).write()) - .toList(); - } - - List _generateTransactionMethods( - final List transactionMethods, - ) { - return transactionMethods - .map((method) => TransactionMethodWriter(library, method).write()) - .toList(); - } - List _generateCreateTableSqlStatements(final List entities) { return entities .map((entity) => entity.getCreateTableStatement(library)) .toList(); } + + List _generateDaos(final Database database) { + return database + .getDaos(library) + .map((dao) => DaoWriter(library, dao).write()) + .toList(); + } } diff --git a/floor_generator/lib/writer/transaction_method_writer.dart b/floor_generator/lib/writer/transaction_method_writer.dart index 2c4daeb4..e261773e 100644 --- a/floor_generator/lib/writer/transaction_method_writer.dart +++ b/floor_generator/lib/writer/transaction_method_writer.dart @@ -32,7 +32,7 @@ class TransactionMethodWriter implements Writer { } else { await (database as sqflite.Database).transaction((transaction) async { final transactionDatabase = _\$${method.databaseName}()..database = transaction; - await transactionDatabase.$methodCall; + await transactionDatabase.${method.daoFieldName}.$methodCall; }); } '''; diff --git a/floor_generator/test/insert_method_writer_test.dart b/floor_generator/test/insert_method_writer_test.dart index d8f0134f..e8b53cfe 100644 --- a/floor_generator/test/insert_method_writer_test.dart +++ b/floor_generator/test/insert_method_writer_test.dart @@ -1,7 +1,7 @@ import 'package:build_test/build_test.dart'; import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/misc/type_utils.dart'; -import 'package:floor_generator/model/database.dart'; +import 'package:floor_generator/model/dao.dart'; import 'package:floor_generator/writer/change_method_writer.dart'; import 'package:floor_generator/writer/insert_method_body_writer.dart'; import 'package:source_gen/source_gen.dart'; @@ -165,9 +165,8 @@ Future _generateInsertMethod(final String methodSignature) async { import 'package:floor_annotation/floor_annotation.dart'; - @Database(version: 1) - abstract class TestDatabase extends FloorDatabase { - static Future openDatabase() async => _\$open(); + @dao + abstract class PersonDao { $methodSignature } @@ -186,13 +185,12 @@ Future _generateInsertMethod(final String methodSignature) async { return LibraryReader(await resolver.findLibraryByName('test')); }); - final databaseClass = library.classes - .where((clazz) => - clazz.isAbstract && clazz.metadata.any(isDatabaseAnnotation)) + final daoClass = library.classes + .where((clazz) => clazz.isAbstract && clazz.metadata.any(isDaoAnnotation)) .first; - final database = Database(databaseClass); - final insertMethod = database.insertMethods.first; + final dao = Dao(daoClass, 'personDao', 'TestDatabase'); + final insertMethod = dao.insertMethods.first; final writer = InsertMethodBodyWriter(library, insertMethod); return ChangeMethodWriter(library, insertMethod, writer).write(); } diff --git a/floor_test/test/dao/dog_dao.dart b/floor_test/test/dao/dog_dao.dart new file mode 100644 index 00000000..21d72d4a --- /dev/null +++ b/floor_test/test/dao/dog_dao.dart @@ -0,0 +1,13 @@ +part of '../database.dart'; + +@dao +abstract class DogDao { + @insert + Future insertDog(Dog dog); + + @Query('SELECT * FROM dog WHERE owner_id = :id') + Future findDogForPersonId(int id); + + @Query('SELECT * FROM dog') + Future> findAllDogs(); +} diff --git a/floor_test/test/dao/person_dao.dart b/floor_test/test/dao/person_dao.dart new file mode 100644 index 00000000..a37d1864 --- /dev/null +++ b/floor_test/test/dao/person_dao.dart @@ -0,0 +1,64 @@ +part of '../database.dart'; + +@dao +abstract class PersonDao { + @Query('SELECT * FROM person') + Future> findAllPersons(); + + @Query('SELECT * FROM person') + Stream> findAllPersonsAsStream(); + + @Query('SELECT * FROM person WHERE id = :id') + Future findPersonById(int id); + + @Query('SELECT * FROM person WHERE id = :id') + Stream findPersonByIdAsStream(int id); + + @Query('SELECT * FROM person WHERE id = :id AND custom_name = :name') + Future findPersonByIdAndName(int id, String name); + + @Insert(onConflict: OnConflictStrategy.REPLACE) + Future insertPerson(Person person); + + @insert + Future insertPersons(List persons); + + @insert + Future insertPersonWithReturn(Person person); + + @insert + Future> insertPersonsWithReturn(List persons); + + @update + Future updatePerson(Person person); + + @update + Future updatePersons(List persons); + + @update + Future updatePersonWithReturn(Person person); + + @update + Future updatePersonsWithReturn(List persons); + + @delete + Future deletePerson(Person person); + + @delete + Future deletePersons(List person); + + @delete + Future deletePersonWithReturn(Person person); + + @delete + Future deletePersonsWithReturn(List persons); + + @transaction + Future replacePersons(List persons) async { + await deleteAllPersons(); + await insertPersons(persons); + } + + @Query('DELETE FROM person') + Future deleteAllPersons(); +} diff --git a/floor_test/test/database.dart b/floor_test/test/database.dart index f42342a7..982835c9 100644 --- a/floor_test/test/database.dart +++ b/floor_test/test/database.dart @@ -1,13 +1,14 @@ +import 'dart:async'; + import 'package:floor/floor.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart' as sqflite; +part 'dao/dog_dao.dart'; +part 'dao/person_dao.dart'; part 'database.g.dart'; - part 'model/address.dart'; - part 'model/dog.dart'; - part 'model/person.dart'; @Database(version: 2) @@ -15,72 +16,7 @@ abstract class TestDatabase extends FloorDatabase { static Future openDatabase(List migrations) async => _$open(migrations); - @Query('SELECT * FROM person') - Future> findAllPersons(); - - @Query('SELECT * FROM person') - Stream> findAllPersonsAsStream(); - - @Query('SELECT * FROM person WHERE id = :id') - Future findPersonById(int id); - - @Query('SELECT * FROM person WHERE id = :id') - Stream findPersonByIdAsStream(int id); - - @Query('SELECT * FROM person WHERE id = :id AND custom_name = :name') - Future findPersonByIdAndName(int id, String name); - - @Insert(onConflict: OnConflictStrategy.REPLACE) - Future insertPerson(Person person); - - @insert - Future insertPersons(List persons); - - @insert - Future insertPersonWithReturn(Person person); - - @insert - Future> insertPersonsWithReturn(List persons); - - @update - Future updatePerson(Person person); - - @update - Future updatePersons(List persons); - - @update - Future updatePersonWithReturn(Person person); - - @update - Future updatePersonsWithReturn(List persons); - - @delete - Future deletePerson(Person person); - - @delete - Future deletePersons(List person); - - @delete - Future deletePersonWithReturn(Person person); - - @delete - Future deletePersonsWithReturn(List persons); - - @transaction - Future replacePersons(List persons) async { - await deleteAllPersons(); - await insertPersons(persons); - } - - @insert - Future insertDog(Dog dog); - - @Query('SELECT * FROM dog WHERE owner_id = :id') - Future findDogForPersonId(int id); - - @Query('SELECT * FROM dog') - Future> findAllDogs(); + PersonDao get personDao; - @Query('DELETE FROM person') - Future deleteAllPersons(); + DogDao get dogDao; } diff --git a/floor_test/test/database_test.dart b/floor_test/test/database_test.dart index 4c776acf..39051793 100644 --- a/floor_test/test/database_test.dart +++ b/floor_test/test/database_test.dart @@ -9,6 +9,8 @@ import 'database.dart'; void main() { group('database tests', () { TestDatabase database; + PersonDao personDao; + DogDao dogDao; setUpAll(() async { final migration1to2 = Migration(1, 2, (database) { @@ -17,6 +19,8 @@ void main() { final allMigrations = [migration1to2]; database = await TestDatabase.openDatabase(allMigrations); + personDao = database.personDao; + dogDao = database.dogDao; await database.database.execute('DELETE FROM dog'); await database.database.execute('DELETE FROM person'); @@ -28,7 +32,7 @@ void main() { }); test('database initially is empty', () async { - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, isEmpty); }); @@ -36,31 +40,31 @@ void main() { group('change single item', () { test('insert person', () async { final person = Person(null, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, hasLength(1)); }); test('delete person', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - await database.deletePerson(person); + await personDao.deletePerson(person); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, isEmpty); }); test('update person', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); final updatedPerson = Person(person.id, _reverse(person.name)); - await database.updatePerson(updatedPerson); + await personDao.updatePerson(updatedPerson); - final actual = await database.findPersonById(person.id); + final actual = await personDao.findPersonById(person.id); expect(actual, equals(updatedPerson)); }); }); @@ -69,32 +73,32 @@ void main() { test('insert persons', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, equals(persons)); }); test('delete persons', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - await database.deletePersons(persons); + await personDao.deletePersons(persons); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, isEmpty); }); test('update persons', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); final updatedPersons = persons .map((person) => Person(person.id, _reverse(person.name))) .toList(); - await database.updatePersons(updatedPersons); + await personDao.updatePersons(updatedPersons); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, equals(updatedPersons)); }); }); @@ -102,12 +106,12 @@ void main() { group('transaction', () { test('replace persons in transaction', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); final newPersons = [Person(3, 'Paul'), Person(4, 'Karl')]; - await database.replacePersons(newPersons); + await personDao.replacePersons(newPersons); - final actual = await database.findAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, equals(newPersons)); }); }); @@ -116,7 +120,7 @@ void main() { test('insert person and return id of inserted item', () async { final person = Person(1, 'Simon'); - final actual = await database.insertPersonWithReturn(person); + final actual = await personDao.insertPersonWithReturn(person); expect(actual, equals(person.id)); }); @@ -124,7 +128,7 @@ void main() { test('insert persons and return ids of inserted items', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - final actual = await database.insertPersonsWithReturn(persons); + final actual = await personDao.insertPersonsWithReturn(persons); final expected = persons.map((person) => person.id).toList(); expect(actual, equals(expected)); @@ -132,44 +136,44 @@ void main() { test('update person and return 1 (affected row count)', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); final updatedPerson = Person(person.id, _reverse(person.name)); - final actual = await database.updatePersonWithReturn(updatedPerson); + final actual = await personDao.updatePersonWithReturn(updatedPerson); - final persistentPerson = await database.findPersonById(person.id); + final persistentPerson = await personDao.findPersonById(person.id); expect(persistentPerson, equals(updatedPerson)); expect(actual, equals(1)); }); test('update persons and return affected rows count', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); final updatedPersons = persons .map((person) => Person(person.id, _reverse(person.name))) .toList(); - final actual = await database.updatePersonsWithReturn(updatedPersons); + final actual = await personDao.updatePersonsWithReturn(updatedPersons); - final persistentPersons = await database.findAllPersons(); + final persistentPersons = await personDao.findAllPersons(); expect(persistentPersons, equals(updatedPersons)); expect(actual, equals(2)); }); test('delete person and return 1 (affected row count)', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - final actual = await database.deletePersonWithReturn(person); + final actual = await personDao.deletePersonWithReturn(person); expect(actual, equals(1)); }); test('delete persons and return affected rows count', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - final actual = await database.deletePersonsWithReturn(persons); + final actual = await personDao.deletePersonsWithReturn(persons); expect(actual, equals(2)); }); @@ -179,28 +183,28 @@ void main() { test('foreign key constraint failed exception', () { final dog = Dog(null, 'Peter', 'Pete', 2); - expect(() => database.insertDog(dog), _throwsDatabaseException); + expect(() => dogDao.insertDog(dog), _throwsDatabaseException); }); test('find dog for person', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); final dog = Dog(2, 'Peter', 'Pete', person.id); - await database.insertDog(dog); + await dogDao.insertDog(dog); - final actual = await database.findDogForPersonId(person.id); + final actual = await dogDao.findDogForPersonId(person.id); expect(actual, equals(dog)); }); test('cascade delete dog on deletion of person', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); final dog = Dog(2, 'Peter', 'Pete', person.id); - await database.insertDog(dog); + await dogDao.insertDog(dog); - await database.deletePerson(person); - final actual = await database.findAllDogs(); + await personDao.deletePerson(person); + final actual = await dogDao.findAllDogs(); expect(actual, isEmpty); }); @@ -209,10 +213,10 @@ void main() { group('query with void return', () { test('delete all persons', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - await database.deleteAllPersons(); - final actual = await database.findAllPersons(); + await personDao.deleteAllPersons(); + final actual = await personDao.findAllPersons(); expect(actual, isEmpty); }); @@ -221,9 +225,9 @@ void main() { group('stream queries', () { test('initially emit persistent data', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); expect(actual, emits([person])); }); @@ -232,18 +236,18 @@ void main() { test('find person by id as stream', () async { final person = Person(1, 'Simon'); - final actual = database.findPersonByIdAsStream(person.id); + final actual = personDao.findPersonByIdAsStream(person.id); - await database.insertPerson(person); + await personDao.insertPerson(person); expect(actual, emits(person)); }); test('find all persons as stream', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); - await database.insertPersons(persons); + await personDao.insertPersons(persons); expect( actual, emitsInOrder(>[[], persons]), @@ -253,11 +257,11 @@ 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')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); - await database.insertPersons(persons2); + await personDao.insertPersons(persons2); expect( actual, emitsInOrder(>[persons, persons + persons2]), @@ -268,12 +272,12 @@ void main() { group('update change', () { test('update item', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); final updatedPerson = Person(person.id, 'Frank'); - await database.updatePerson(updatedPerson); + await personDao.updatePerson(updatedPerson); expect( actual, emitsInOrder(>[ @@ -287,11 +291,11 @@ void main() { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; final updatedPersons = persons.map((person) => Person(person.id, 'Nick')).toList(); - await database.insertPersons(persons); + await personDao.insertPersons(persons); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); - await database.updatePersons(updatedPersons); + await personDao.updatePersons(updatedPersons); expect(actual, emitsInOrder(>[persons, updatedPersons])); }); }); @@ -299,11 +303,11 @@ void main() { group('deletion change', () { test('delete item', () async { final person = Person(1, 'Simon'); - await database.insertPerson(person); + await personDao.insertPerson(person); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); - await database.deletePerson(person); + await personDao.deletePerson(person); expect( actual, emitsInOrder(>[ @@ -315,11 +319,11 @@ void main() { test('delete items', () async { final persons = [Person(1, 'Simon'), Person(2, 'Frank')]; - await database.insertPersons(persons); + await personDao.insertPersons(persons); - final actual = database.findAllPersonsAsStream(); + final actual = personDao.findAllPersonsAsStream(); - await database.deletePersons(persons); + await personDao.deletePersons(persons); expect(actual, emitsInOrder(>[persons, []])); }); });