forked from openfoodfacts/smooth-app
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: openfoodfacts#2280 - DaoProduct's just migrated to sqflite (ope…
…nfoodfacts#2289) New files: * `abstract_sql_dao.dart`: DAO abstraction for SQL. * `bulk_deletable.dart`: Interface for bulk database deletes. * `bulk_insertable.dart`: Interface for bulk database inserts. * `bulk_manager.dart`: Manager for bulk database inserts and deletes. * `dao_hive_product.dart`: moved code from the previous `hive` version of `DaoProduct`, renamed here `DaoHiveProduct` Impacted files: * `dao_product.dart`: `sqflite` version of `DaoProduct`, that is from now on the only one to be used * `local_database.dart`: added `sqflite` and its one table so far, also named `DaoProduct` in order to minimize the changes everywhere else (the old `DaoProduct` being renamed `DaoHiveProduct`); migrated the product data from `hive` to `sqflite` * `Podfile.lock`: wtf * `pubspec.lock`: wtf * `data_importer/pubspec.yaml`: updated the `sqflite` dependency to the same as in `smooth_app` * `smooth_app/pubspec.yaml`: added a dependency to `sqflite`
- Loading branch information
1 parent
347c39c
commit 1662685
Showing
11 changed files
with
404 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import 'package:smooth_app/database/local_database.dart'; | ||
|
||
/// DAO abstraction for SQL. | ||
abstract class AbstractSqlDao { | ||
AbstractSqlDao(this.localDatabase); | ||
|
||
final LocalDatabase localDatabase; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import 'package:smooth_app/database/bulk_insertable.dart'; | ||
|
||
/// Interface for bulk database deletes. | ||
/// | ||
/// cf. [BulkManager], [BulkInsertable] | ||
abstract class BulkDeletable implements BulkInsertable { | ||
/// "where" clause for delete in bulk mode | ||
String getDeleteWhere(final List<dynamic> deleteWhereArgs); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import 'package:smooth_app/database/bulk_manager.dart'; | ||
|
||
/// Interface for bulk database inserts. | ||
/// | ||
/// cf. [BulkManager] | ||
abstract class BulkInsertable { | ||
/// Insert columns for bulk mode | ||
List<String> getInsertColumns(); | ||
|
||
/// Table name | ||
String getTableName(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import 'dart:async'; | ||
import 'dart:math'; | ||
|
||
import 'package:smooth_app/database/bulk_deletable.dart'; | ||
import 'package:smooth_app/database/bulk_insertable.dart'; | ||
import 'package:sqflite/sqflite.dart'; | ||
|
||
/// Manager for bulk database inserts and deletes. | ||
/// | ||
/// In tests it looked 33% faster to use delete/insert rather than upsert | ||
/// And of course it's much faster to perform bulk actions | ||
/// rather than numerous single actions | ||
/// cf. [BulkInsertable], [BulkDeletable] | ||
class BulkManager { | ||
/// Max number of parameters in a SQFlite query | ||
/// | ||
/// cf. SQLITE_MAX_VARIABLE_NUMBER, "which defaults to 999" | ||
// TODO(monsieurtanuki): find a way to retrieve this number from SQFlite system tables, cf. https://github.com/tekartik/sqflite/issues/663 | ||
static const int SQLITE_MAX_VARIABLE_NUMBER = 999; | ||
|
||
/// Returns the number of inserted rows by optimized bulk insert | ||
Future<int> insert({ | ||
required final BulkInsertable bulkInsertable, | ||
required final List<dynamic> parameters, | ||
required final DatabaseExecutor databaseExecutor, | ||
}) async { | ||
int result = 0; | ||
final String tableName = bulkInsertable.getTableName(); | ||
final List<String> columnNames = bulkInsertable.getInsertColumns(); | ||
final int numCols = columnNames.length; | ||
if (parameters.isEmpty) { | ||
return result; | ||
} | ||
if (columnNames.isEmpty) { | ||
throw Exception('There must be at least one column!'); | ||
} | ||
if (parameters.length % numCols != 0) { | ||
throw Exception( | ||
'Parameter list size (${parameters.length}) cannot be divided by $numCols'); | ||
} | ||
final String variables = '?${',?' * (columnNames.length - 1)}'; | ||
final int maxSlice = (SQLITE_MAX_VARIABLE_NUMBER ~/ numCols) * numCols; | ||
for (int start = 0; start < parameters.length; start += maxSlice) { | ||
final int size = min(parameters.length - start, maxSlice); | ||
final int insertedRows = size ~/ numCols; | ||
final int additionalRecordsNumber = -1 + insertedRows; | ||
await databaseExecutor.rawInsert( | ||
'insert into $tableName(${columnNames.join(',')}) ' | ||
'values($variables)${',($variables)' * additionalRecordsNumber}', | ||
parameters.sublist(start, start + size), | ||
); | ||
result += insertedRows; | ||
} | ||
return result; | ||
} | ||
|
||
/// Returns the number of deleted rows by optimized bulk delete | ||
Future<int> delete({ | ||
required final BulkDeletable bulkDeletable, | ||
required final List<dynamic> parameters, | ||
required final DatabaseExecutor databaseExecutor, | ||
final List<dynamic>? additionalParameters, | ||
}) async { | ||
int result = 0; | ||
final String tableName = bulkDeletable.getTableName(); | ||
if (parameters.isEmpty) { | ||
return result; | ||
} | ||
final int maxSlice = | ||
SQLITE_MAX_VARIABLE_NUMBER - (additionalParameters?.length ?? 0); | ||
for (int start = 0; start < parameters.length; start += maxSlice) { | ||
final int size = min(parameters.length - start, maxSlice); | ||
final List<dynamic> currentParameters = <dynamic>[]; | ||
if (additionalParameters != null && additionalParameters.isNotEmpty) { | ||
currentParameters.addAll(additionalParameters); | ||
} | ||
currentParameters.addAll(parameters.sublist(start, start + size)); | ||
final int deleted = await databaseExecutor.delete( | ||
tableName, | ||
where: bulkDeletable.getDeleteWhere(currentParameters), | ||
whereArgs: currentParameters, | ||
); | ||
result += deleted; | ||
} | ||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import 'dart:async'; | ||
import 'dart:convert'; | ||
import 'package:hive/hive.dart'; | ||
import 'package:openfoodfacts/model/Product.dart'; | ||
import 'package:smooth_app/database/abstract_dao.dart'; | ||
import 'package:smooth_app/database/local_database.dart'; | ||
|
||
/// Hive type adapter for [Product] | ||
class _ProductAdapter extends TypeAdapter<Product> { | ||
@override | ||
final int typeId = 1; | ||
|
||
@override | ||
Product read(BinaryReader reader) => | ||
Product.fromJson(jsonDecode(reader.readString()) as Map<String, dynamic>); | ||
|
||
@override | ||
void write(BinaryWriter writer, Product obj) => | ||
writer.writeString(jsonEncode(obj.toJson())); | ||
} | ||
|
||
/// Where we store the products as "barcode => product". | ||
@Deprecated('use [DaoProduct] instead') | ||
class DaoHiveProduct extends AbstractDao { | ||
@Deprecated('use [DaoProduct] instead') | ||
DaoHiveProduct(final LocalDatabase localDatabase) : super(localDatabase); | ||
|
||
static const String _hiveBoxName = 'products'; | ||
|
||
@override | ||
Future<void> init() async => Hive.openLazyBox<Product>(_hiveBoxName); | ||
|
||
@override | ||
void registerAdapter() => Hive.registerAdapter(_ProductAdapter()); | ||
|
||
LazyBox<Product> _getBox() => Hive.lazyBox<Product>(_hiveBoxName); | ||
|
||
Future<Product?> get(final String barcode) async => _getBox().get(barcode); | ||
|
||
Future<Map<String, Product>> getAll(final List<String> barcodes) async { | ||
final LazyBox<Product> box = _getBox(); | ||
final Map<String, Product> result = <String, Product>{}; | ||
for (final String barcode in barcodes) { | ||
final Product? product = await box.get(barcode); | ||
if (product != null) { | ||
result[barcode] = product; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
Future<void> put(final Product product) async => putAll(<Product>[product]); | ||
|
||
Future<void> putAll(final Iterable<Product> products) async { | ||
final Map<String, Product> upserts = <String, Product>{}; | ||
for (final Product product in products) { | ||
upserts[product.barcode!] = product; | ||
} | ||
await _getBox().putAll(upserts); | ||
} | ||
|
||
Future<List<String>> getAllKeys() async { | ||
final LazyBox<Product> box = _getBox(); | ||
final List<String> result = <String>[]; | ||
for (final dynamic key in box.keys) { | ||
result.add(key.toString()); | ||
} | ||
return result; | ||
} | ||
|
||
// Just for the migration | ||
Future<void> deleteAll(final List<String> barcodes) async { | ||
final LazyBox<Product> box = _getBox(); | ||
await box.deleteAll(barcodes); | ||
} | ||
} |
Oops, something went wrong.