mirror of https://github.com/AMT-Cheif/drift.git
ffi: Remove isolate proxy
This commit is contained in:
parent
e83464df28
commit
d39f2d9769
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
- Remove the `background` flag from the moor apis provided by this package. Use the moor isolate api
|
- Remove the `background` flag from the moor apis provided by this package. Use the moor isolate api
|
||||||
instead.
|
instead.
|
||||||
|
- Remove builtin support for background execution from the low-level `Database` api
|
||||||
- Support Dart 2.6, drop support for older versions
|
- Support Dart 2.6, drop support for older versions
|
||||||
|
|
||||||
## 0.0.1
|
## 0.0.1
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'package:moor_ffi/database.dart';
|
|
||||||
|
|
||||||
const _createTable = r'''
|
|
||||||
CREATE TABLE frameworks (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name VARCHAR NOT NULL
|
|
||||||
);
|
|
||||||
''';
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
final db = await IsolateDb.openMemory();
|
|
||||||
await db.execute(_createTable);
|
|
||||||
|
|
||||||
final insertStmt =
|
|
||||||
await db.prepare('INSERT INTO frameworks(name) VALUES (?)');
|
|
||||||
await insertStmt.execute(['Flutter']);
|
|
||||||
await insertStmt.execute(['AngularDart']);
|
|
||||||
await insertStmt.close();
|
|
||||||
|
|
||||||
final selectStmt = await db.prepare('SELECT * FROM frameworks ORDER BY name');
|
|
||||||
final result = await selectStmt.select();
|
|
||||||
for (var row in result) {
|
|
||||||
print('${row['id']}: ${row['name']}');
|
|
||||||
}
|
|
||||||
|
|
||||||
await selectStmt.close();
|
|
||||||
await db.close();
|
|
||||||
}
|
|
|
@ -1,11 +1,9 @@
|
||||||
/// Exports the low-level [Database] and [IsolateDb] classes to run operations
|
/// Exports the low-level [Database] class to run operations on a sqlite
|
||||||
/// on a sqflite database.
|
/// database via `dart:ffi`.
|
||||||
library database;
|
library database;
|
||||||
|
|
||||||
import 'package:moor_ffi/src/bindings/types.dart';
|
import 'package:moor_ffi/src/bindings/types.dart';
|
||||||
import 'src/impl/isolate/isolate_db.dart';
|
|
||||||
|
|
||||||
export 'src/api/database.dart';
|
|
||||||
export 'src/api/result.dart';
|
export 'src/api/result.dart';
|
||||||
export 'src/impl/database.dart' show SqliteException, Database;
|
export 'src/impl/database.dart'
|
||||||
export 'src/impl/isolate/isolate_db.dart';
|
show SqliteException, Database, PreparedStatement;
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:moor_ffi/database.dart';
|
|
||||||
|
|
||||||
/// A opened sqlite database.
|
|
||||||
abstract class BaseDatabase {
|
|
||||||
/// Closes this database connection and releases the resources it uses. If
|
|
||||||
/// an error occurs while closing the database, an exception will be thrown.
|
|
||||||
/// The allocated memory will be freed either way.
|
|
||||||
FutureOr<void> close();
|
|
||||||
|
|
||||||
/// Executes the [sql] statement and ignores the result. Will throw if an
|
|
||||||
/// error occurs while executing.
|
|
||||||
FutureOr<void> execute(String sql);
|
|
||||||
|
|
||||||
/// Prepares the [sql] statement.
|
|
||||||
FutureOr<BasePreparedStatement> prepare(String sql);
|
|
||||||
|
|
||||||
/// Get the application defined version of this database.
|
|
||||||
FutureOr<int> userVersion();
|
|
||||||
|
|
||||||
/// Update the application defined version of this database.
|
|
||||||
FutureOr<void> setUserVersion(int version);
|
|
||||||
|
|
||||||
/// Returns the amount of rows affected by the last INSERT, UPDATE or DELETE
|
|
||||||
/// statement.
|
|
||||||
FutureOr<int> getUpdatedRows();
|
|
||||||
|
|
||||||
/// Returns the row-id of the last inserted row.
|
|
||||||
FutureOr<int> getLastInsertId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A prepared statement that can be executed multiple times.
|
|
||||||
abstract class BasePreparedStatement {
|
|
||||||
/// Executes this prepared statement as a select statement. The returned rows
|
|
||||||
/// will be returned.
|
|
||||||
FutureOr<Result> select([List<dynamic> args]);
|
|
||||||
|
|
||||||
/// Executes this prepared statement.
|
|
||||||
FutureOr<void> execute([List<dynamic> params]);
|
|
||||||
|
|
||||||
/// Closes this prepared statement and releases its resources.
|
|
||||||
FutureOr<void> close();
|
|
||||||
}
|
|
|
@ -16,7 +16,8 @@ part 'prepared_statement.dart';
|
||||||
|
|
||||||
const _openingFlags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE;
|
const _openingFlags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE;
|
||||||
|
|
||||||
class Database implements BaseDatabase {
|
/// A opened sqlite database.
|
||||||
|
class Database {
|
||||||
final Pointer<types.Database> _db;
|
final Pointer<types.Database> _db;
|
||||||
final List<PreparedStatement> _preparedStmt = [];
|
final List<PreparedStatement> _preparedStmt = [];
|
||||||
bool _isClosed = false;
|
bool _isClosed = false;
|
||||||
|
@ -56,7 +57,9 @@ class Database implements BaseDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Closes this database connection and releases the resources it uses. If
|
||||||
|
/// an error occurs while closing the database, an exception will be thrown.
|
||||||
|
/// The allocated memory will be freed either way.
|
||||||
void close() {
|
void close() {
|
||||||
// close all prepared statements first
|
// close all prepared statements first
|
||||||
_isClosed = true;
|
_isClosed = true;
|
||||||
|
@ -84,7 +87,8 @@ class Database implements BaseDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Executes the [sql] statement and ignores the result. Will throw if an
|
||||||
|
/// error occurs while executing.
|
||||||
void execute(String sql) {
|
void execute(String sql) {
|
||||||
_ensureOpen();
|
_ensureOpen();
|
||||||
|
|
||||||
|
@ -111,7 +115,7 @@ class Database implements BaseDatabase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Prepares the [sql] statement.
|
||||||
PreparedStatement prepare(String sql) {
|
PreparedStatement prepare(String sql) {
|
||||||
_ensureOpen();
|
_ensureOpen();
|
||||||
|
|
||||||
|
@ -137,7 +141,7 @@ class Database implements BaseDatabase {
|
||||||
return prepared;
|
return prepared;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Get the application defined version of this database.
|
||||||
int userVersion() {
|
int userVersion() {
|
||||||
final stmt = prepare('PRAGMA user_version');
|
final stmt = prepare('PRAGMA user_version');
|
||||||
final result = stmt.select();
|
final result = stmt.select();
|
||||||
|
@ -146,18 +150,19 @@ class Database implements BaseDatabase {
|
||||||
return result.first.columnAt(0) as int;
|
return result.first.columnAt(0) as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Update the application defined version of this database.
|
||||||
void setUserVersion(int version) {
|
void setUserVersion(int version) {
|
||||||
execute('PRAGMA user_version = $version');
|
execute('PRAGMA user_version = $version');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Returns the amount of rows affected by the last INSERT, UPDATE or DELETE
|
||||||
|
/// statement.
|
||||||
int getUpdatedRows() {
|
int getUpdatedRows() {
|
||||||
_ensureOpen();
|
_ensureOpen();
|
||||||
return bindings.sqlite3_changes(_db);
|
return bindings.sqlite3_changes(_db);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Returns the row-id of the last inserted row.
|
||||||
int getLastInsertId() {
|
int getLastInsertId() {
|
||||||
_ensureOpen();
|
_ensureOpen();
|
||||||
return bindings.sqlite3_last_insert_rowid(_db);
|
return bindings.sqlite3_last_insert_rowid(_db);
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:moor_ffi/database.dart';
|
|
||||||
import 'package:moor_ffi/src/impl/database.dart';
|
|
||||||
|
|
||||||
enum IsolateCommandType {
|
|
||||||
openDatabase,
|
|
||||||
closeDatabase,
|
|
||||||
executeSqlDirectly,
|
|
||||||
prepareStatement,
|
|
||||||
getUserVersion,
|
|
||||||
setUserVersion,
|
|
||||||
getUpdatedRows,
|
|
||||||
getLastInsertId,
|
|
||||||
preparedSelect,
|
|
||||||
preparedExecute,
|
|
||||||
preparedClose
|
|
||||||
}
|
|
||||||
|
|
||||||
class IsolateCommand {
|
|
||||||
final int requestId;
|
|
||||||
final IsolateCommandType type;
|
|
||||||
final dynamic data;
|
|
||||||
|
|
||||||
/// If this command operates on a prepared statement, contains the id of that
|
|
||||||
/// statement as sent by the background isolate.
|
|
||||||
int preparedStatementId;
|
|
||||||
|
|
||||||
IsolateCommand(this.requestId, this.type, this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
class IsolateResponse {
|
|
||||||
final int requestId;
|
|
||||||
final dynamic response;
|
|
||||||
final dynamic error;
|
|
||||||
|
|
||||||
IsolateResponse(this.requestId, this.response, this.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Communicates with a background isolate over an RPC-like api.
|
|
||||||
class DbOperationProxy {
|
|
||||||
/// Stream of messages received by the background isolate.
|
|
||||||
final StreamController<dynamic> backgroundMsgs;
|
|
||||||
final ReceivePort _receivePort;
|
|
||||||
final Map<int, Completer> _pendingRequests = {};
|
|
||||||
|
|
||||||
final SendPort send;
|
|
||||||
final Isolate isolate;
|
|
||||||
|
|
||||||
var closed = false;
|
|
||||||
|
|
||||||
int _currentRequestId = 0;
|
|
||||||
|
|
||||||
DbOperationProxy(
|
|
||||||
this.backgroundMsgs, this._receivePort, this.send, this.isolate) {
|
|
||||||
backgroundMsgs.stream.listen(_handleResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> sendRequest(IsolateCommandType type, dynamic data,
|
|
||||||
{int preparedStmtId}) {
|
|
||||||
if (closed) {
|
|
||||||
throw StateError('Tried to call a database method after .close()');
|
|
||||||
}
|
|
||||||
|
|
||||||
final id = _currentRequestId++;
|
|
||||||
final cmd = IsolateCommand(id, type, data)
|
|
||||||
..preparedStatementId = preparedStmtId;
|
|
||||||
final completer = Completer();
|
|
||||||
_pendingRequests[id] = completer;
|
|
||||||
|
|
||||||
send.send(cmd);
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleResponse(dynamic response) {
|
|
||||||
if (response is IsolateResponse) {
|
|
||||||
final completer = _pendingRequests.remove(response.requestId);
|
|
||||||
if (response.error != null) {
|
|
||||||
completer.completeError(response.error);
|
|
||||||
} else {
|
|
||||||
completer.complete(response.response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
closed = true;
|
|
||||||
_receivePort.close();
|
|
||||||
backgroundMsgs.close();
|
|
||||||
isolate.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<DbOperationProxy> spawn() async {
|
|
||||||
final foregroundReceive = ReceivePort();
|
|
||||||
final backgroundSend = foregroundReceive.sendPort;
|
|
||||||
final isolate = await Isolate.spawn(_entryPoint, backgroundSend,
|
|
||||||
debugName: 'moor_ffi background isolate');
|
|
||||||
|
|
||||||
final controller = StreamController.broadcast();
|
|
||||||
foregroundReceive.listen(controller.add);
|
|
||||||
|
|
||||||
final foregroundSend = await controller.stream
|
|
||||||
.firstWhere((msg) => msg is SendPort) as SendPort;
|
|
||||||
|
|
||||||
return DbOperationProxy(
|
|
||||||
controller, foregroundReceive, foregroundSend, isolate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _entryPoint(SendPort backgroundSend) {
|
|
||||||
final backgroundReceive = ReceivePort();
|
|
||||||
final foregroundSend = backgroundReceive.sendPort;
|
|
||||||
|
|
||||||
// inform the main isolate about the created send port
|
|
||||||
backgroundSend.send(foregroundSend);
|
|
||||||
|
|
||||||
BackgroundIsolateRunner(backgroundReceive, backgroundSend).start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BackgroundIsolateRunner {
|
|
||||||
final ReceivePort receive;
|
|
||||||
final SendPort send;
|
|
||||||
|
|
||||||
Database db;
|
|
||||||
List<PreparedStatement> stmts = [];
|
|
||||||
|
|
||||||
BackgroundIsolateRunner(this.receive, this.send);
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
receive.listen((data) {
|
|
||||||
if (data is IsolateCommand) {
|
|
||||||
try {
|
|
||||||
final response = _handleCommand(data);
|
|
||||||
send.send(IsolateResponse(data.requestId, response, null));
|
|
||||||
} catch (e) {
|
|
||||||
if (e is Error) {
|
|
||||||
// errors contain a StackTrace, which cannot be sent. So we just
|
|
||||||
// send the description of that stacktrace.
|
|
||||||
final exception =
|
|
||||||
Exception('Error in background isolate: $e\n${e.stackTrace}');
|
|
||||||
send.send(IsolateResponse(data.requestId, null, exception));
|
|
||||||
} else {
|
|
||||||
send.send(IsolateResponse(data.requestId, null, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic _handleCommand(IsolateCommand cmd) {
|
|
||||||
switch (cmd.type) {
|
|
||||||
case IsolateCommandType.openDatabase:
|
|
||||||
assert(db == null);
|
|
||||||
db = Database.open(cmd.data as String);
|
|
||||||
break;
|
|
||||||
case IsolateCommandType.closeDatabase:
|
|
||||||
db?.close();
|
|
||||||
stmts.clear();
|
|
||||||
db = null;
|
|
||||||
break;
|
|
||||||
case IsolateCommandType.executeSqlDirectly:
|
|
||||||
db.execute(cmd.data as String);
|
|
||||||
break;
|
|
||||||
case IsolateCommandType.prepareStatement:
|
|
||||||
final stmt = db.prepare(cmd.data as String);
|
|
||||||
stmts.add(stmt);
|
|
||||||
return stmts.length - 1;
|
|
||||||
case IsolateCommandType.getUserVersion:
|
|
||||||
return db.userVersion();
|
|
||||||
case IsolateCommandType.setUserVersion:
|
|
||||||
final version = cmd.data as int;
|
|
||||||
db.setUserVersion(version);
|
|
||||||
break;
|
|
||||||
case IsolateCommandType.getUpdatedRows:
|
|
||||||
return db.getUpdatedRows();
|
|
||||||
case IsolateCommandType.getLastInsertId:
|
|
||||||
return db.getLastInsertId();
|
|
||||||
case IsolateCommandType.preparedSelect:
|
|
||||||
final stmt = stmts[cmd.preparedStatementId];
|
|
||||||
return stmt.select(cmd.data as List);
|
|
||||||
case IsolateCommandType.preparedExecute:
|
|
||||||
final stmt = stmts[cmd.preparedStatementId];
|
|
||||||
stmt.execute(cmd.data as List);
|
|
||||||
break;
|
|
||||||
case IsolateCommandType.preparedClose:
|
|
||||||
final index = cmd.preparedStatementId;
|
|
||||||
stmts[index].close();
|
|
||||||
stmts.removeAt(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:moor_ffi/database.dart';
|
|
||||||
import 'package:moor_ffi/src/impl/database.dart';
|
|
||||||
import 'package:moor_ffi/src/impl/isolate/background.dart';
|
|
||||||
|
|
||||||
class IsolateDb implements BaseDatabase {
|
|
||||||
/// Spawns a background isolate and opens the [file] on that isolate. The file
|
|
||||||
/// will be created if it doesn't exist.
|
|
||||||
static Future<IsolateDb> openFile(File file) => open(file.absolute.path);
|
|
||||||
|
|
||||||
/// Opens a in-memory database on a background isolates.
|
|
||||||
///
|
|
||||||
/// If you're not using extensive queries, a synchronous [Database] will
|
|
||||||
/// provide better performance for in-memory databases!
|
|
||||||
static Future<IsolateDb> openMemory() => open(':memory:');
|
|
||||||
|
|
||||||
/// Spawns a background isolate and opens a sqlite3 database from its
|
|
||||||
/// filename.
|
|
||||||
static Future<IsolateDb> open(String path) async {
|
|
||||||
final proxy = await DbOperationProxy.spawn();
|
|
||||||
|
|
||||||
final isolate = IsolateDb._(proxy);
|
|
||||||
await isolate._open(path);
|
|
||||||
|
|
||||||
return isolate;
|
|
||||||
}
|
|
||||||
|
|
||||||
final DbOperationProxy _proxy;
|
|
||||||
IsolateDb._(this._proxy);
|
|
||||||
|
|
||||||
Future<int> _sendAndAssumeInt(IsolateCommandType type, [dynamic data]) async {
|
|
||||||
return await _proxy.sendRequest(type, data) as int;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _open(String path) {
|
|
||||||
return _proxy.sendRequest(IsolateCommandType.openDatabase, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await _proxy.sendRequest(IsolateCommandType.closeDatabase, null);
|
|
||||||
_proxy.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> execute(String sql) async {
|
|
||||||
await _proxy.sendRequest(IsolateCommandType.executeSqlDirectly, sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> getLastInsertId() async {
|
|
||||||
return _sendAndAssumeInt(IsolateCommandType.getLastInsertId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> getUpdatedRows() async {
|
|
||||||
return _sendAndAssumeInt(IsolateCommandType.getUpdatedRows);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<BasePreparedStatement> prepare(String sql) async {
|
|
||||||
final id =
|
|
||||||
await _sendAndAssumeInt(IsolateCommandType.prepareStatement, sql);
|
|
||||||
return IsolatePreparedStatement(this, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> setUserVersion(int version) async {
|
|
||||||
await _proxy.sendRequest(IsolateCommandType.setUserVersion, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> userVersion() async {
|
|
||||||
return _sendAndAssumeInt(IsolateCommandType.getUserVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IsolatePreparedStatement implements BasePreparedStatement {
|
|
||||||
final IsolateDb _db;
|
|
||||||
final int _id;
|
|
||||||
|
|
||||||
IsolatePreparedStatement(this._db, this._id);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await _db._proxy.sendRequest(IsolateCommandType.preparedClose, null,
|
|
||||||
preparedStmtId: _id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> execute([List params]) async {
|
|
||||||
await _db._proxy.sendRequest(IsolateCommandType.preparedExecute, params,
|
|
||||||
preparedStmtId: _id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Result> select([List params]) async {
|
|
||||||
final response = await _db._proxy.sendRequest(
|
|
||||||
IsolateCommandType.preparedSelect, params,
|
|
||||||
preparedStmtId: _id);
|
|
||||||
return response as Result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
part of 'database.dart';
|
part of 'database.dart';
|
||||||
|
|
||||||
class PreparedStatement implements BasePreparedStatement {
|
/// A prepared statement that can be executed multiple times.
|
||||||
|
class PreparedStatement {
|
||||||
final Pointer<types.Statement> _stmt;
|
final Pointer<types.Statement> _stmt;
|
||||||
final Database _db;
|
final Database _db;
|
||||||
bool _closed = false;
|
bool _closed = false;
|
||||||
|
@ -10,7 +11,7 @@ class PreparedStatement implements BasePreparedStatement {
|
||||||
|
|
||||||
PreparedStatement._(this._stmt, this._db);
|
PreparedStatement._(this._stmt, this._db);
|
||||||
|
|
||||||
@override
|
/// Closes this prepared statement and releases its resources.
|
||||||
void close() {
|
void close() {
|
||||||
if (!_closed) {
|
if (!_closed) {
|
||||||
_reset();
|
_reset();
|
||||||
|
@ -26,7 +27,8 @@ class PreparedStatement implements BasePreparedStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Executes this prepared statement as a select statement. The returned rows
|
||||||
|
/// will be returned.
|
||||||
Result select([List<dynamic> params]) {
|
Result select([List<dynamic> params]) {
|
||||||
_ensureNotFinalized();
|
_ensureNotFinalized();
|
||||||
_reset();
|
_reset();
|
||||||
|
@ -71,7 +73,7 @@ class PreparedStatement implements BasePreparedStatement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Executes this prepared statement.
|
||||||
void execute([List<dynamic> params]) {
|
void execute([List<dynamic> params]) {
|
||||||
_ensureNotFinalized();
|
_ensureNotFinalized();
|
||||||
_reset();
|
_reset();
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:moor_ffi/database.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('insert statements report their id', () {
|
||||||
|
final opened = Database.memory();
|
||||||
|
opened.execute('CREATE TABLE tbl(a INTEGER PRIMARY KEY AUTOINCREMENT)');
|
||||||
|
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
opened.execute('INSERT INTO tbl DEFAULT VALUES');
|
||||||
|
expect(opened.getLastInsertId(), i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
opened.close();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:moor_ffi/database.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('prepared statements can be used multiple times', () {
|
||||||
|
final opened = Database.memory();
|
||||||
|
opened.execute('CREATE TABLE tbl (a TEXT);');
|
||||||
|
|
||||||
|
final stmt = opened.prepare('INSERT INTO tbl(a) VALUES(?)');
|
||||||
|
stmt.execute(['a']);
|
||||||
|
stmt.execute(['b']);
|
||||||
|
stmt.close();
|
||||||
|
|
||||||
|
final select = opened.prepare('SELECT * FROM tbl ORDER BY a');
|
||||||
|
final result = select.select();
|
||||||
|
|
||||||
|
expect(result, hasLength(2));
|
||||||
|
expect(result.map((row) => row['a']), ['a', 'b']);
|
||||||
|
|
||||||
|
select.close();
|
||||||
|
|
||||||
|
opened.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepared statements cannot be used after close', () {
|
||||||
|
final opened = Database.memory();
|
||||||
|
|
||||||
|
final stmt = opened.prepare('SELECT ?');
|
||||||
|
stmt.close();
|
||||||
|
|
||||||
|
expect(stmt.select, throwsA(anything));
|
||||||
|
|
||||||
|
opened.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepared statements cannot be used after db is closed', () {
|
||||||
|
final opened = Database.memory();
|
||||||
|
final stmt = opened.prepare('SELECT 1');
|
||||||
|
opened.close();
|
||||||
|
|
||||||
|
expect(stmt.select, throwsA(anything));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:moor_ffi/database.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('select statements return expected value', () {
|
||||||
|
final opened = Database.memory();
|
||||||
|
|
||||||
|
final prepared = opened.prepare('SELECT ?');
|
||||||
|
|
||||||
|
final result1 = prepared.select([1]);
|
||||||
|
expect(result1.columnNames, ['?']);
|
||||||
|
expect(result1.single.columnAt(0), 1);
|
||||||
|
|
||||||
|
final result2 = prepared.select([2]);
|
||||||
|
expect(result2.columnNames, ['?']);
|
||||||
|
expect(result2.single.columnAt(0), 2);
|
||||||
|
|
||||||
|
opened.close();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:moor_ffi/database.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('can set the user version on a database', () {
|
||||||
|
final file = File(p.join(
|
||||||
|
Directory.systemTemp.absolute.path, 'moor_ffi_test_user_version.db'));
|
||||||
|
final opened = Database.openFile(file);
|
||||||
|
|
||||||
|
var version = opened.userVersion();
|
||||||
|
expect(version, 0);
|
||||||
|
|
||||||
|
opened.setUserVersion(3);
|
||||||
|
version = opened.userVersion();
|
||||||
|
expect(version, 3);
|
||||||
|
|
||||||
|
// ensure that the version is stored on file
|
||||||
|
opened.close();
|
||||||
|
|
||||||
|
final another = Database.openFile(file);
|
||||||
|
expect(another.userVersion(), 3);
|
||||||
|
another.close();
|
||||||
|
|
||||||
|
file.deleteSync();
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,68 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'package:moor_ffi/database.dart';
|
|
||||||
|
|
||||||
import 'suite/insert.dart' as insert;
|
|
||||||
import 'suite/prepared_statements.dart' as prepared_statements;
|
|
||||||
import 'suite/select.dart' as select;
|
|
||||||
import 'suite/user_version.dart' as user_version;
|
|
||||||
|
|
||||||
var _tempFileCounter = 0;
|
|
||||||
List<File> _createdFiles = [];
|
|
||||||
File temporaryFile() {
|
|
||||||
final count = _tempFileCounter++;
|
|
||||||
final path =
|
|
||||||
p.join(Directory.systemTemp.absolute.path, 'moor_ffi_test_$count.db');
|
|
||||||
final file = File(path);
|
|
||||||
_createdFiles.add(file);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class TestedDatabase {
|
|
||||||
FutureOr<BaseDatabase> openFile(File file);
|
|
||||||
FutureOr<BaseDatabase> openMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestRegularDatabase implements TestedDatabase {
|
|
||||||
@override
|
|
||||||
BaseDatabase openFile(File file) => Database.openFile(file);
|
|
||||||
|
|
||||||
@override
|
|
||||||
BaseDatabase openMemory() => Database.memory();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestIsolateDatabase implements TestedDatabase {
|
|
||||||
@override
|
|
||||||
Future<BaseDatabase> openFile(File file) => IsolateDb.openFile(file);
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<BaseDatabase> openMemory() => IsolateDb.openMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('regular database', () {
|
|
||||||
_declareAll(TestRegularDatabase());
|
|
||||||
});
|
|
||||||
|
|
||||||
group('isolate database', () {
|
|
||||||
_declareAll(TestIsolateDatabase());
|
|
||||||
});
|
|
||||||
|
|
||||||
tearDownAll(() async {
|
|
||||||
for (var file in _createdFiles) {
|
|
||||||
if (await file.exists()) {
|
|
||||||
await file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _declareAll(TestedDatabase db) {
|
|
||||||
insert.main(db);
|
|
||||||
prepared_statements.main(db);
|
|
||||||
select.main(db);
|
|
||||||
user_version.main(db);
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import '../ffi_test.dart';
|
|
||||||
|
|
||||||
void main(TestedDatabase db) {
|
|
||||||
test('insert statements report their id', () async {
|
|
||||||
final opened = await db.openMemory();
|
|
||||||
await opened
|
|
||||||
.execute('CREATE TABLE tbl(a INTEGER PRIMARY KEY AUTOINCREMENT)');
|
|
||||||
|
|
||||||
for (var i = 0; i < 5; i++) {
|
|
||||||
await opened.execute('INSERT INTO tbl DEFAULT VALUES');
|
|
||||||
expect(await opened.getLastInsertId(), i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await opened.close();
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import '../ffi_test.dart';
|
|
||||||
|
|
||||||
void main(TestedDatabase db) {
|
|
||||||
test('prepared statements can be used multiple times', () async {
|
|
||||||
final opened = await db.openMemory();
|
|
||||||
await opened.execute('CREATE TABLE tbl (a TEXT);');
|
|
||||||
|
|
||||||
final stmt = await opened.prepare('INSERT INTO tbl(a) VALUES(?)');
|
|
||||||
await stmt.execute(['a']);
|
|
||||||
await stmt.execute(['b']);
|
|
||||||
await stmt.close();
|
|
||||||
|
|
||||||
final select = await opened.prepare('SELECT * FROM tbl ORDER BY a');
|
|
||||||
final result = await select.select();
|
|
||||||
|
|
||||||
expect(result, hasLength(2));
|
|
||||||
expect(result.map((row) => row['a']), ['a', 'b']);
|
|
||||||
|
|
||||||
await select.close();
|
|
||||||
|
|
||||||
await opened.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prepared statements cannot be used after close', () async {
|
|
||||||
final opened = await db.openMemory();
|
|
||||||
|
|
||||||
final stmt = await opened.prepare('SELECT ?');
|
|
||||||
await stmt.close();
|
|
||||||
|
|
||||||
expect(stmt.select, throwsA(anything));
|
|
||||||
|
|
||||||
await opened.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('prepared statements cannot be used after db is closed', () async {
|
|
||||||
final opened = await db.openMemory();
|
|
||||||
final stmt = await opened.prepare('SELECT 1');
|
|
||||||
await opened.close();
|
|
||||||
|
|
||||||
expect(stmt.select, throwsA(anything));
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import '../ffi_test.dart';
|
|
||||||
|
|
||||||
void main(TestedDatabase db) {
|
|
||||||
test('select statements return expected value', () async {
|
|
||||||
final opened = await db.openMemory();
|
|
||||||
|
|
||||||
final prepared = await opened.prepare('SELECT ?');
|
|
||||||
|
|
||||||
final result1 = await prepared.select([1]);
|
|
||||||
expect(result1.columnNames, ['?']);
|
|
||||||
expect(result1.single.columnAt(0), 1);
|
|
||||||
|
|
||||||
final result2 = await prepared.select([2]);
|
|
||||||
expect(result2.columnNames, ['?']);
|
|
||||||
expect(result2.single.columnAt(0), 2);
|
|
||||||
|
|
||||||
await opened.close();
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
import '../ffi_test.dart';
|
|
||||||
|
|
||||||
void main(TestedDatabase db) {
|
|
||||||
test('can set the user version on a database', () async {
|
|
||||||
final file = temporaryFile();
|
|
||||||
final opened = await db.openFile(file);
|
|
||||||
|
|
||||||
var version = await opened.userVersion();
|
|
||||||
expect(version, 0);
|
|
||||||
|
|
||||||
await opened.setUserVersion(3);
|
|
||||||
version = await opened.userVersion();
|
|
||||||
expect(version, 3);
|
|
||||||
|
|
||||||
// ensure that the version is stored on file
|
|
||||||
await opened.close();
|
|
||||||
|
|
||||||
final another = await db.openFile(file);
|
|
||||||
expect(await another.userVersion(), 3);
|
|
||||||
await another.close();
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue