Skip to content

Commit

Permalink
Feature: Add support for @DatabaseView annotations
Browse files Browse the repository at this point in the history
This adds initial support for @DatabaseView annotations known from room. One test was added, further testing is needed. Implements pinchbv#130.
  • Loading branch information
mqus committed Feb 23, 2020
1 parent 8996e96 commit 457e697
Show file tree
Hide file tree
Showing 23 changed files with 448 additions and 52 deletions.
3 changes: 2 additions & 1 deletion floor/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ environment:
dependencies:
path: ^1.6.4
sqflite: ^1.2.0
floor_annotation: ^0.6.0
floor_annotation:
path: ../floor_annotation
meta: ^1.1.8
flutter:
sdk: flutter
Expand Down
1 change: 1 addition & 0 deletions floor_annotation/lib/floor_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ library floor_annotation;
export 'src/column_info.dart';
export 'src/dao.dart';
export 'src/database.dart';
export 'src/database_view.dart';
export 'src/delete.dart';
export 'src/entity.dart';
export 'src/foreign_key.dart';
Expand Down
6 changes: 5 additions & 1 deletion floor_annotation/lib/src/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class Database {
/// The entities the database manages.
final List<Type> entities;

/// The views the database manages.
final List<Type> views;

/// Marks a class as a FloorDatabase.
const Database({@required this.version, @required this.entities});
const Database(
{@required this.version, @required this.entities, this.views = const []});
}
14 changes: 14 additions & 0 deletions floor_annotation/lib/src/database_view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// Marks a class as a database view (a fixed select statement).
class DatabaseView {
/// The table name of the SQLite view.
final String viewName;

/// The SELECT query on which the view is based on.
final String query;

/// Marks a class as a database entity (table).
const DatabaseView(
this.query, {
this.viewName,
});
}
4 changes: 4 additions & 0 deletions floor_generator/lib/misc/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ abstract class AnnotationField {

static const DATABASE_VERSION = 'version';
static const DATABASE_ENTITIES = 'entities';
static const DATABASE_VIEWS = 'views';

static const COLUMN_INFO_NAME = 'name';
static const COLUMN_INFO_NULLABLE = 'nullable';
Expand All @@ -13,6 +14,9 @@ abstract class AnnotationField {
static const ENTITY_FOREIGN_KEYS = 'foreignKeys';
static const ENTITY_INDICES = 'indices';
static const ENTITY_PRIMARY_KEYS = 'primaryKeys';

static const VIEW_NAME = 'viewName';
static const VIEW_QUERY = 'query';
}

abstract class ForeignKeyField {
Expand Down
15 changes: 11 additions & 4 deletions floor_generator/lib/processor/dao_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import 'package:floor_generator/processor/query_method_processor.dart';
import 'package:floor_generator/processor/transaction_method_processor.dart';
import 'package:floor_generator/processor/update_method_processor.dart';
import 'package:floor_generator/value_object/dao.dart';
import 'package:floor_generator/value_object/view.dart';
import 'package:floor_generator/value_object/deletion_method.dart';
import 'package:floor_generator/value_object/entity.dart';
import 'package:floor_generator/value_object/insertion_method.dart';
import 'package:floor_generator/value_object/query_method.dart';
import 'package:floor_generator/value_object/queryable.dart';
import 'package:floor_generator/value_object/transaction_method.dart';
import 'package:floor_generator/value_object/update_method.dart';

Expand All @@ -21,20 +23,24 @@ class DaoProcessor extends Processor<Dao> {
final String _daoGetterName;
final String _databaseName;
final List<Entity> _entities;
final List<View> _views;

DaoProcessor(
final ClassElement classElement,
final String daoGetterName,
final String databaseName,
final List<Entity> entities,
final List<View> views,
) : assert(classElement != null),
assert(daoGetterName != null),
assert(databaseName != null),
assert(entities != null),
assert(views != null),
_classElement = classElement,
_daoGetterName = daoGetterName,
_databaseName = databaseName,
_entities = entities;
_entities = entities,
_views = views;

@override
Dao process() {
Expand Down Expand Up @@ -64,7 +70,8 @@ class DaoProcessor extends Processor<Dao> {
List<QueryMethod> _getQueryMethods(final List<MethodElement> methods) {
return methods
.where((method) => method.hasAnnotation(annotations.Query))
.map((method) => QueryMethodProcessor(method, _entities).process())
.map((method) =>
QueryMethodProcessor(method, _entities, _views).process())
.toList();
}

Expand Down Expand Up @@ -114,10 +121,10 @@ class DaoProcessor extends Processor<Dao> {
.toList();
}

List<Entity> _getStreamEntities(final List<QueryMethod> queryMethods) {
List<Queryable> _getStreamEntities(final List<QueryMethod> queryMethods) {
return queryMethods
.where((method) => method.returnsStream)
.map((method) => method.entity)
.map((method) => method.queryable)
.toList();
}
}
33 changes: 30 additions & 3 deletions floor_generator/lib/processor/database_processor.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations
show Database, dao, Entity;
show Database, dao, Entity, DatabaseView;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/constants.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/dao_processor.dart';
import 'package:floor_generator/processor/entity_processor.dart';
import 'package:floor_generator/processor/error/database_processor_error.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/processor/view_processor.dart';
import 'package:floor_generator/value_object/dao_getter.dart';
import 'package:floor_generator/value_object/database.dart';
import 'package:floor_generator/value_object/view.dart';
import 'package:floor_generator/value_object/entity.dart';

class DatabaseProcessor extends Processor<Database> {
Expand All @@ -27,10 +29,12 @@ class DatabaseProcessor extends Processor<Database> {
Database process() {
final databaseName = _classElement.displayName;
final entities = _getEntities(_classElement);
final daoGetters = _getDaoGetters(databaseName, entities);
final views = _getViews(_classElement);
final daoGetters = _getDaoGetters(databaseName, entities, views);
final version = _getDatabaseVersion();

return Database(_classElement, databaseName, entities, daoGetters, version);
return Database(
_classElement, databaseName, entities, views, daoGetters, version);
}

@nonNull
Expand All @@ -50,6 +54,7 @@ class DatabaseProcessor extends Processor<Database> {
List<DaoGetter> _getDaoGetters(
final String databaseName,
final List<Entity> entities,
final List<View> views,
) {
return _classElement.fields.where(_isDao).map((field) {
final classElement = field.type.element as ClassElement;
Expand All @@ -60,6 +65,7 @@ class DatabaseProcessor extends Processor<Database> {
name,
databaseName,
entities,
views,
).process();

return DaoGetter(field, name, dao);
Expand Down Expand Up @@ -97,9 +103,30 @@ class DatabaseProcessor extends Processor<Database> {
return entities;
}

@nonNull
List<View> _getViews(final ClassElement databaseClassElement) {
final views = _classElement
.getAnnotation(annotations.Database)
.getField(AnnotationField.DATABASE_VIEWS)
?.toListValue()
?.map((object) => object.toTypeValue().element)
?.whereType<ClassElement>()
?.where(_isView)
?.map((classElement) => ViewProcessor(classElement).process())
?.toList();

return views;
}

@nonNull
bool _isEntity(final ClassElement classElement) {
return classElement.hasAnnotation(annotations.Entity) &&
!classElement.isAbstract;
}

@nonNull
bool _isView(final ClassElement classElement) {
return classElement.hasAnnotation(annotations.DatabaseView) &&
!classElement.isAbstract;
}
}
19 changes: 19 additions & 0 deletions floor_generator/lib/processor/error/view_processor_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:source_gen/source_gen.dart';

class ViewProcessorError {
final ClassElement _classElement;

ViewProcessorError(final ClassElement classElement)
: assert(classElement != null),
_classElement = classElement;

InvalidGenerationSourceError get MISSING_QUERY {
return InvalidGenerationSourceError(
'There is no SELECT Query defined on the entity ${_classElement.displayName}.',
todo:
'Define a SELECT query for this View with @DatabaseView(\'SELECT [...]\') ',
element: _classElement,
);
}
}
23 changes: 17 additions & 6 deletions floor_generator/lib/processor/query_method_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@ import 'package:floor_generator/misc/constants.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/error/query_method_processor_error.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/value_object/view.dart';
import 'package:floor_generator/value_object/entity.dart';
import 'package:floor_generator/value_object/query_method.dart';
import 'package:floor_generator/value_object/queryable.dart';

class QueryMethodProcessor extends Processor<QueryMethod> {
final QueryMethodProcessorError _processorError;

final MethodElement _methodElement;
final List<Entity> _entities;
final List<View> _views;

QueryMethodProcessor(
final MethodElement methodElement,
final List<Entity> entities,
final List<View> views,
) : assert(methodElement != null),
assert(entities != null),
assert(views != null),
_methodElement = methodElement,
_entities = entities,
_views = views,
_processorError = QueryMethodProcessorError(methodElement);

@nonNull
Expand All @@ -42,11 +48,16 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
returnsStream,
);

final entity = _entities.firstWhere(
(entity) =>
entity.classElement.displayName ==
flattenedReturnType.getDisplayString(),
orElse: () => null); // doesn't return an entity
final Queryable queryable = _entities.firstWhere(
(entity) =>
entity.classElement.displayName ==
flattenedReturnType.getDisplayString(),
orElse: () => null) ??
_views.firstWhere(
(view) =>
view.classElement.displayName ==
flattenedReturnType.getDisplayString(),
orElse: () => null);

return QueryMethod(
_methodElement,
Expand All @@ -55,7 +66,7 @@ class QueryMethodProcessor extends Processor<QueryMethod> {
rawReturnType,
flattenedReturnType,
parameters,
entity,
queryable,
);
}

Expand Down
Loading

0 comments on commit 457e697

Please sign in to comment.