Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add support for @DatabaseView annotations #262

Merged
merged 7 commits into from
Mar 15, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions floor/test/integration/dao/name_dao.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:floor/floor.dart';

import '../model/name.dart';

@dao
abstract class NameDao {
mqus marked this conversation as resolved.
Show resolved Hide resolved
@Query('SELECT * FROM names ORDER BY name ASC')
Future<List<Name>> findAllNames();

@Query('SELECT * FROM names WHERE name = :name')
Future<Name> findExactName(String name);

@Query('SELECT * FROM names WHERE name LIKE :suffix ORDER BY name ASC')
Future<List<Name>> findNamesLike(String suffix);
}
23 changes: 23 additions & 0 deletions floor/test/integration/model/name.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:floor/floor.dart';

@DatabaseView(
'SELECT custom_name as name FROM person UNION SELECT name from dog',
viewName: 'names')
class Name {
final String name;

Name(this.name);

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Name && runtimeType == other.runtimeType && name == other.name;

@override
int get hashCode => name.hashCode;

@override
String toString() {
return 'Name{name: $name}';
}
}
95 changes: 95 additions & 0 deletions floor/test/integration/view_test/view_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'dart:async';

import 'package:floor/floor.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' hide equals;
import 'package:sqflite/sqflite.dart' as sqflite;
import 'package:sqflite_ffi_test/sqflite_ffi_test.dart';

import '../dao/dog_dao.dart';
import '../dao/name_dao.dart';
import '../dao/person_dao.dart';
import '../model/dog.dart';
import '../model/name.dart';
import '../model/person.dart';

part 'view_test.g.dart';

@Database(version: 2, entities: [Person, Dog], views: [Name])
mqus marked this conversation as resolved.
Show resolved Hide resolved
abstract class ViewTestDatabase extends FloorDatabase {
PersonDao get personDao;

DogDao get dogDao;

NameDao get nameDao;
}

void main() {
TestWidgetsFlutterBinding.ensureInitialized();
sqfliteFfiTestInit();

group('database tests', () {
ViewTestDatabase database;
PersonDao personDao;
DogDao dogDao;
NameDao nameDao;

setUp(() async {
final migration1to2 = Migration(1, 2, (database) {
database.execute('ALTER TABLE dog ADD COLUMN nick_name TEXT');
});
final allMigrations = [migration1to2];

database = await $FloorViewTestDatabase
.inMemoryDatabaseBuilder()
.addMigrations(allMigrations)
.build();
mqus marked this conversation as resolved.
Show resolved Hide resolved

personDao = database.personDao;
dogDao = database.dogDao;
nameDao = database.nameDao;
});

tearDown(() async {
await database.close();
});

group('Query Views', () {
test('query view with exact value', () async {
final person = Person(1, 'Frank');
await personDao.insertPerson(person);

final actual = await nameDao.findExactName('Frank');

final expected = Name('Frank');
expect(actual, equals(expected));
});

test('query view with LIKE', () async {
final persons = [Person(1, 'Leo'), Person(2, 'Frank')];
await personDao.insertPersons(persons);

final dog = Dog(1, 'Romeo', 'Rome', 1);
await dogDao.insertDog(dog);

final actual = await nameDao.findNamesLike('%eo');

final expected = [Name('Leo'), Name('Romeo')];
expect(actual, equals(expected));
});

test('query view with all values', () async {
final persons = [Person(1, 'Leo'), Person(2, 'Frank')];
await personDao.insertPersons(persons);

final dog = Dog(1, 'Romeo', 'Rome', 1);
await dogDao.insertDog(dog);

final actual = await nameDao.findAllNames();

final expected = [Name('Frank'), Name('Leo'), Name('Romeo')];
expect(actual, equals(expected));
});
});
});
}
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
9 changes: 8 additions & 1 deletion floor_annotation/lib/src/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ 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 view (a fixed select statement).
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
12 changes: 9 additions & 3 deletions floor_generator/lib/processor/dao_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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';
Expand All @@ -21,20 +22,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 @@ -66,7 +71,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 @@ -119,7 +125,7 @@ class DaoProcessor extends Processor<Dao> {
List<Entity> _getStreamEntities(final List<QueryMethod> queryMethods) {
return queryMethods
.where((method) => method.returnsStream)
.map((method) => method.entity)
.map((method) => method.queryable as Entity)
.toList();
}
}
37 changes: 34 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,18 @@ 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 +60,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 +71,7 @@ class DatabaseProcessor extends Processor<Database> {
name,
databaseName,
entities,
views,
).process();

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

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

@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;
}
}
Loading