2022-08-14 13:36:22 -07:00
|
|
|
/// Encryption support for drift, built with the [sqflite_sqlcipher](https://github.com/davidmartos96/sqflite_sqlcipher)
|
2019-07-24 10:23:25 -07:00
|
|
|
/// library.
|
2022-08-14 13:36:22 -07:00
|
|
|
library encrypted_drift;
|
2019-07-24 10:23:25 -07:00
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
// this file should be kept in sync with drift_sqflite/lib/drift_sqflite.dart
|
2019-08-18 00:58:04 -07:00
|
|
|
|
2019-07-24 10:23:25 -07:00
|
|
|
import 'dart:async';
|
2019-08-18 00:58:04 -07:00
|
|
|
import 'dart:io';
|
2021-09-10 02:43:21 -07:00
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
import 'package:drift/backends.dart';
|
|
|
|
import 'package:drift/drift.dart';
|
2021-09-10 02:43:21 -07:00
|
|
|
import 'package:path/path.dart';
|
2020-03-15 12:42:30 -07:00
|
|
|
import 'package:sqflite_sqlcipher/sqflite.dart' as s;
|
2019-07-24 10:23:25 -07:00
|
|
|
|
2019-08-18 00:58:04 -07:00
|
|
|
/// Signature of a function that runs when a database doesn't exist on file.
|
|
|
|
/// This can be useful to, for instance, load the database from an asset if it
|
|
|
|
/// doesn't exist.
|
|
|
|
typedef DatabaseCreator = FutureOr<void> Function(File file);
|
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
class _SqfliteDelegate extends DatabaseDelegate {
|
2020-11-25 14:21:17 -08:00
|
|
|
late s.Database db;
|
|
|
|
bool _isOpen = false;
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
final bool inDbFolder;
|
|
|
|
final String path;
|
2020-11-25 14:21:17 -08:00
|
|
|
final String? password;
|
2019-07-24 10:23:25 -07:00
|
|
|
|
2019-08-18 00:58:04 -07:00
|
|
|
bool singleInstance;
|
2020-11-25 14:21:17 -08:00
|
|
|
final DatabaseCreator? creator;
|
2019-08-18 00:58:04 -07:00
|
|
|
|
2020-11-25 14:21:17 -08:00
|
|
|
_SqfliteDelegate(
|
|
|
|
this.inDbFolder,
|
|
|
|
this.path, {
|
|
|
|
this.singleInstance = true,
|
|
|
|
this.creator,
|
|
|
|
this.password,
|
|
|
|
});
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
@override
|
2022-08-14 13:36:22 -07:00
|
|
|
late final DbVersionDelegate versionDelegate = _SqfliteVersionDelegate(db);
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
@override
|
2022-08-14 13:36:22 -07:00
|
|
|
TransactionDelegate get transactionDelegate => const NoTransactionDelegate();
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
@override
|
2020-11-25 14:21:17 -08:00
|
|
|
bool get isOpen => _isOpen;
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
@override
|
2020-03-15 06:55:02 -07:00
|
|
|
Future<void> open(QueryExecutorUser user) async {
|
2019-07-24 10:23:25 -07:00
|
|
|
String resolvedPath;
|
|
|
|
if (inDbFolder) {
|
|
|
|
resolvedPath = join(await s.getDatabasesPath(), path);
|
|
|
|
} else {
|
|
|
|
resolvedPath = path;
|
|
|
|
}
|
|
|
|
|
2019-08-18 00:58:04 -07:00
|
|
|
final file = File(resolvedPath);
|
|
|
|
if (creator != null && !await file.exists()) {
|
2020-11-25 14:21:17 -08:00
|
|
|
await creator!(file);
|
2019-08-18 00:58:04 -07:00
|
|
|
}
|
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
// default value when no migration happened
|
2020-03-15 06:55:02 -07:00
|
|
|
db = await s.openDatabase(
|
2019-07-24 10:23:25 -07:00
|
|
|
resolvedPath,
|
|
|
|
password: password,
|
2019-08-18 00:58:04 -07:00
|
|
|
singleInstance: singleInstance,
|
2019-07-24 10:23:25 -07:00
|
|
|
);
|
2020-11-25 14:21:17 -08:00
|
|
|
_isOpen = true;
|
2019-07-24 10:23:25 -07:00
|
|
|
}
|
2019-08-18 00:58:04 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> close() {
|
|
|
|
return db.close();
|
|
|
|
}
|
2019-07-24 10:23:25 -07:00
|
|
|
|
|
|
|
@override
|
2020-11-25 14:21:17 -08:00
|
|
|
Future<void> runCustom(String statement, List<Object?> args) {
|
2020-03-08 12:15:22 -07:00
|
|
|
return db.execute(statement, args);
|
2019-07-24 10:23:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2020-11-25 14:21:17 -08:00
|
|
|
Future<int> runInsert(String statement, List<Object?> args) {
|
2019-07-24 10:23:25 -07:00
|
|
|
return db.rawInsert(statement, args);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2020-11-25 14:21:17 -08:00
|
|
|
Future<QueryResult> runSelect(String statement, List<Object?> args) async {
|
2019-07-24 10:23:25 -07:00
|
|
|
final result = await db.rawQuery(statement, args);
|
|
|
|
return QueryResult.fromRows(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2020-11-25 14:21:17 -08:00
|
|
|
Future<int> runUpdate(String statement, List<Object?> args) {
|
2019-07-24 10:23:25 -07:00
|
|
|
return db.rawUpdate(statement, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
class _SqfliteVersionDelegate extends DynamicVersionDelegate {
|
|
|
|
final s.Database _db;
|
|
|
|
|
|
|
|
_SqfliteVersionDelegate(this._db);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<int> get schemaVersion async {
|
|
|
|
final result = await _db.rawQuery('PRAGMA user_version;');
|
|
|
|
return result.single.values.first as int;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> setSchemaVersion(int version) async {
|
|
|
|
await _db.rawUpdate('PRAGMA user_version = $version;');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A query executor that uses an encrypted version of sqflite internally.
|
2019-07-24 10:23:25 -07:00
|
|
|
class EncryptedExecutor extends DelegatedDatabase {
|
2019-08-18 00:58:04 -07:00
|
|
|
/// A query executor that will store the database in the file declared by
|
|
|
|
/// [path]. If [logStatements] is true, statements sent to the database will
|
|
|
|
/// be [print]ed, which can be handy for debugging. The [singleInstance]
|
|
|
|
/// parameter sets the corresponding parameter on [s.openDatabase].
|
|
|
|
/// The [creator] will be called when the database file doesn't exist. It can
|
|
|
|
/// be used to, for instance, populate default data from an asset. Note that
|
|
|
|
/// migrations might behave differently when populating the database this way.
|
|
|
|
/// For instance, a database created by an [creator] will not receive the
|
|
|
|
/// [MigrationStrategy.onCreate] callback because it hasn't been created by
|
2022-08-14 13:36:22 -07:00
|
|
|
/// drift.
|
2019-07-24 10:23:25 -07:00
|
|
|
EncryptedExecutor(
|
2020-11-25 14:21:17 -08:00
|
|
|
{required String path,
|
|
|
|
required String password,
|
|
|
|
bool? logStatements,
|
|
|
|
bool singleInstance = true,
|
|
|
|
DatabaseCreator? creator})
|
2019-08-18 00:58:04 -07:00
|
|
|
: super(
|
2022-08-14 13:36:22 -07:00
|
|
|
_SqfliteDelegate(
|
|
|
|
false,
|
|
|
|
path,
|
|
|
|
singleInstance: singleInstance,
|
|
|
|
creator: creator,
|
|
|
|
password: password,
|
|
|
|
),
|
2019-07-24 10:23:25 -07:00
|
|
|
logStatements: logStatements);
|
|
|
|
|
2019-08-18 00:58:04 -07:00
|
|
|
/// A query executor that will store the database in the file declared by
|
|
|
|
/// [path], which will be resolved relative to [s.getDatabasesPath()].
|
|
|
|
/// If [logStatements] is true, statements sent to the database will
|
|
|
|
/// be [print]ed, which can be handy for debugging. The [singleInstance]
|
|
|
|
/// parameter sets the corresponding parameter on [s.openDatabase].
|
|
|
|
/// The [creator] will be called when the database file doesn't exist. It can
|
|
|
|
/// be used to, for instance, populate default data from an asset. Note that
|
|
|
|
/// migrations might behave differently when populating the database this way.
|
|
|
|
/// For instance, a database created by an [creator] will not receive the
|
|
|
|
/// [MigrationStrategy.onCreate] callback because it hasn't been created by
|
2022-08-14 13:36:22 -07:00
|
|
|
/// drift.
|
2019-07-24 10:23:25 -07:00
|
|
|
EncryptedExecutor.inDatabaseFolder(
|
2020-11-25 14:21:17 -08:00
|
|
|
{required String path,
|
|
|
|
required String password,
|
|
|
|
bool? logStatements,
|
|
|
|
bool singleInstance = true,
|
|
|
|
DatabaseCreator? creator})
|
2019-08-18 00:58:04 -07:00
|
|
|
: super(
|
2022-08-14 13:36:22 -07:00
|
|
|
_SqfliteDelegate(
|
|
|
|
true,
|
|
|
|
path,
|
|
|
|
singleInstance: singleInstance,
|
|
|
|
creator: creator,
|
|
|
|
password: password,
|
|
|
|
),
|
2019-07-24 10:23:25 -07:00
|
|
|
logStatements: logStatements);
|
2020-03-02 11:46:43 -08:00
|
|
|
|
2022-08-14 13:36:22 -07:00
|
|
|
/// The underlying sqflite [s.Database] object used by drift to send queries.
|
2020-03-02 11:46:43 -08:00
|
|
|
///
|
2022-08-14 13:36:22 -07:00
|
|
|
/// Using the sqflite database can cause unexpected behavior in drift. For
|
2020-03-02 11:46:43 -08:00
|
|
|
/// instance, stream queries won't update for updates sent to the [s.Database]
|
2022-08-14 13:36:22 -07:00
|
|
|
/// directly. Further, drift assumes full control over the database for its
|
|
|
|
/// internal connection management.
|
2020-03-02 11:46:43 -08:00
|
|
|
/// For this reason, projects shouldn't use this getter unless they absolutely
|
2022-08-14 13:36:22 -07:00
|
|
|
/// need to. The database is exposed to make migrating from sqflite to drift
|
2020-03-02 11:46:43 -08:00
|
|
|
/// easier.
|
|
|
|
///
|
2022-08-14 13:36:22 -07:00
|
|
|
/// Note that this returns null until the drifft database has been opened.
|
|
|
|
/// A drift database is opened lazily when the first query runs.
|
2020-11-25 14:21:17 -08:00
|
|
|
s.Database? get sqfliteDb {
|
2020-03-02 11:46:43 -08:00
|
|
|
final sqfliteDelegate = delegate as _SqfliteDelegate;
|
2020-11-25 14:21:17 -08:00
|
|
|
return sqfliteDelegate.isOpen ? sqfliteDelegate.db : null;
|
2020-03-02 11:46:43 -08:00
|
|
|
}
|
2021-04-01 08:43:23 -07:00
|
|
|
|
|
|
|
@override
|
|
|
|
// We're not really required to be sequential since sqflite has an internal
|
|
|
|
// lock to bring statements into a sequential order.
|
2022-08-14 13:36:22 -07:00
|
|
|
// Setting isSequential here helps with cancellations in stream queries
|
2021-04-01 08:43:23 -07:00
|
|
|
// though.
|
|
|
|
bool get isSequential => true;
|
2019-07-24 10:23:25 -07:00
|
|
|
}
|