Skip to content

Commit

Permalink
Add database adapters (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitusortner authored Mar 3, 2019
1 parent e15ffc0 commit f2685b6
Show file tree
Hide file tree
Showing 28 changed files with 1,226 additions and 348 deletions.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ linter:
# the Dart Lint rules page to make maintenance easier
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
- always_declare_return_types
- always_put_control_body_on_new_line
# - always_put_control_body_on_new_line
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
# - always_require_non_null_named_parameters
# - always_specify_types
Expand Down
5 changes: 5 additions & 0 deletions floor/lib/floor.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
library floor;

export 'package:floor/src/adapter/deletion_adapter.dart';
export 'package:floor/src/adapter/insertion_adapter.dart';
export 'package:floor/src/adapter/migration_adapter.dart';
export 'package:floor/src/adapter/query_adapter.dart';
export 'package:floor/src/adapter/update_adapter.dart';
export 'package:floor/src/database.dart';
export 'package:floor/src/migration.dart';
export 'package:floor_annotation/floor_annotation.dart';
72 changes: 72 additions & 0 deletions floor/lib/src/adapter/deletion_adapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:sqflite/sqflite.dart';

class DeletionAdapter<T> {
final DatabaseExecutor _database;
final String _entityName;
final String _primaryKeyColumnName;
final Map<String, dynamic> Function(T) _valueMapper;

DeletionAdapter(
final DatabaseExecutor database,
final String entityName,
final String primaryKeyColumnName,
final Map<String, dynamic> Function(T) valueMapper,
) : assert(database != null),
assert(entityName != null),
assert(entityName.isNotEmpty),
assert(primaryKeyColumnName != null),
assert(primaryKeyColumnName.isNotEmpty),
assert(valueMapper != null),
_database = database,
_entityName = entityName,
_primaryKeyColumnName = primaryKeyColumnName,
_valueMapper = valueMapper;

Future<void> delete(final T item) async {
await _delete(item);
}

Future<void> deleteList(final List<T> items) async {
if (items.isEmpty) return;

final batch = _database.batch();
_deleteList(batch, items);
await batch.commit(noResult: true);
}

Future<int> deleteAndReturnChangedRows(final T item) {
return _delete(item);
}

Future<int> deleteListAndReturnChangedRows(final List<T> items) async {
if (items.isEmpty) return 0;

final batch = _database.batch();
_deleteList(batch, items);
return (await batch.commit(noResult: false))
.cast<int>()
.reduce((sum, element) => sum + element);
}

Future<int> _delete(final T item) {
final int primaryKey = _valueMapper(item)[_primaryKeyColumnName];

return _database.delete(
_entityName,
where: '$_primaryKeyColumnName = ?',
whereArgs: <int>[primaryKey],
);
}

void _deleteList(final Batch batch, final List items) {
for (final item in items) {
final int primaryKey = _valueMapper(item)[_primaryKeyColumnName];

batch.delete(
_entityName,
where: '$_primaryKeyColumnName = ?',
whereArgs: <int>[primaryKey],
);
}
}
}
77 changes: 77 additions & 0 deletions floor/lib/src/adapter/insertion_adapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:sqflite/sqflite.dart';

class InsertionAdapter<T> {
final DatabaseExecutor _database;
final String _entityName;
final Map<String, dynamic> Function(T) _valueMapper;

InsertionAdapter(
final DatabaseExecutor database,
final String entityName,
final Map<String, dynamic> Function(T) valueMapper,
) : assert(database != null),
assert(entityName != null),
assert(entityName.isNotEmpty),
assert(valueMapper != null),
_database = database,
_entityName = entityName,
_valueMapper = valueMapper;

Future<void> insert(
final T item,
final ConflictAlgorithm conflictAlgorithm,
) async {
await _insert(item, conflictAlgorithm);
}

Future<void> insertList(
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) async {
if (items.isEmpty) return;

final batch = _database.batch();
_insertList(batch, items, conflictAlgorithm);
await batch.commit(noResult: true);
}

Future<int> insertAndReturnId(
final T item,
final ConflictAlgorithm conflictAlgorithm,
) {
return _insert(item, conflictAlgorithm);
}

Future<List<int>> insertListAndReturnIds(
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) async {
if (items.isEmpty) return [];

final batch = _database.batch();
_insertList(batch, items, conflictAlgorithm);
return (await batch.commit(noResult: false)).cast<int>();
}

Future<int> _insert(final T item, final ConflictAlgorithm conflictAlgorithm) {
return _database.insert(
_entityName,
_valueMapper(item),
conflictAlgorithm: conflictAlgorithm,
);
}

void _insertList(
final Batch batch,
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) {
for (final item in items) {
batch.insert(
_entityName,
_valueMapper(item),
conflictAlgorithm: conflictAlgorithm,
);
}
}
}
30 changes: 30 additions & 0 deletions floor/lib/src/adapter/migration_adapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:floor/src/migration.dart';
import 'package:sqflite/sqflite.dart';

abstract class MigrationAdapter {
/// Runs the given [migrations] for migrating the database schema and data.
static void runMigrations(
final Database migrationDatabase,
final int startVersion,
final int endVersion,
final List<Migration> migrations,
) {
final relevantMigrations = migrations
.where((migration) => migration.startVersion >= startVersion)
.toList()
..sort((first, second) =>
first.startVersion.compareTo(second.startVersion));

if (relevantMigrations.isEmpty ||
relevantMigrations.last.endVersion != endVersion) {
throw StateError(
'There is no migration supplied to update the database to the current version.'
' Aborting the migration.',
);
}

for (final migration in relevantMigrations) {
migration.migrate(migrationDatabase);
}
}
}
37 changes: 37 additions & 0 deletions floor/lib/src/adapter/query_adapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:sqflite/sqflite.dart';

/// This class knows how to execute database queries.
class QueryAdapter {
final DatabaseExecutor _database;

QueryAdapter(final DatabaseExecutor database)
: assert(database != null),
_database = database;

Future<T> query<T>(
final String sql,
final T Function(Map<String, dynamic>) mapper,
) async {
final rows = await _database.rawQuery(sql);

if (rows.isEmpty) {
return null;
} else if (rows.length > 1) {
throw StateError("Query returned more than one row for '$sql'");
}

return mapper(rows.first);
}

Future<List<T>> queryList<T>(
final String sql,
final T Function(Map<String, dynamic>) mapper,
) async {
final rows = await _database.rawQuery(sql);
return rows.map((row) => mapper(row)).toList();
}

Future<void> queryNoReturn(final String sql) async {
await _database.rawQuery(sql);
}
}
94 changes: 94 additions & 0 deletions floor/lib/src/adapter/update_adapter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'package:sqflite/sqflite.dart';

class UpdateAdapter<T> {
final DatabaseExecutor _database;
final String _entityName;
final String _primaryKeyColumnName;
final Map<String, dynamic> Function(T) _valueMapper;

UpdateAdapter(
final DatabaseExecutor database,
final String entityName,
final String primaryKeyColumnName,
final Map<String, dynamic> Function(T) valueMapper,
) : assert(database != null),
assert(entityName != null),
assert(entityName.isNotEmpty),
assert(primaryKeyColumnName != null),
assert(primaryKeyColumnName.isNotEmpty),
assert(valueMapper != null),
_database = database,
_entityName = entityName,
_valueMapper = valueMapper,
_primaryKeyColumnName = primaryKeyColumnName;

Future<void> update(
final T item,
final ConflictAlgorithm conflictAlgorithm,
) async {
await _update(item, conflictAlgorithm);
}

Future<void> updateList(
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) async {
if (items.isEmpty) return;

final batch = _database.batch();
_updateList(batch, items, conflictAlgorithm);
await batch.commit(noResult: true);
}

Future<int> updateAndReturnChangedRows(
final T item,
final ConflictAlgorithm conflictAlgorithm,
) {
return _update(item, conflictAlgorithm);
}

Future<int> updateListAndReturnChangedRows(
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) async {
if (items.isEmpty) return 0;

final batch = _database.batch();
_updateList(batch, items, conflictAlgorithm);
return (await batch.commit(noResult: false))
.cast<int>()
.reduce((sum, element) => sum + element);
}

Future<int> _update(final T item, final ConflictAlgorithm conflictAlgorithm) {
final values = _valueMapper(item);
final int primaryKey = values[_primaryKeyColumnName];

return _database.update(
_entityName,
values,
where: '$_primaryKeyColumnName = ?',
whereArgs: <int>[primaryKey],
conflictAlgorithm: conflictAlgorithm,
);
}

void _updateList(
final Batch batch,
final List<T> items,
final ConflictAlgorithm conflictAlgorithm,
) {
for (final item in items) {
final values = _valueMapper(item);
final int primaryKey = values[_primaryKeyColumnName];

batch.update(
_entityName,
values,
where: '$_primaryKeyColumnName = ?',
whereArgs: <int>[primaryKey],
conflictAlgorithm: conflictAlgorithm,
);
}
}
}
28 changes: 0 additions & 28 deletions floor/lib/src/database.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:floor/floor.dart';
import 'package:meta/meta.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

/// Extend this class to enable database functionality.
Expand All @@ -19,31 +18,4 @@ abstract class FloorDatabase {
await immutableDatabase.close();
}
}

/// Runs the given [migrations] for migrating the database schema and data.
@protected
void runMigrations(
final sqflite.Database migrationDatabase,
final int startVersion,
final int endVersion,
final List<Migration> migrations,
) {
final relevantMigrations = migrations
.where((migration) => migration.startVersion >= startVersion)
.toList()
..sort((first, second) =>
first.startVersion.compareTo(second.startVersion));

if (relevantMigrations.isEmpty ||
relevantMigrations.last.endVersion != endVersion) {
throw StateError(
'There is no migration supplied to update the database to the current version.'
' Aborting the migration.',
);
}

for (final migration in relevantMigrations) {
migration.migrate(migrationDatabase);
}
}
}
Loading

0 comments on commit f2685b6

Please sign in to comment.