mirror of https://github.com/AMT-Cheif/drift.git
Merge branch 'ffi' into develop
# Conflicts: # moor/lib/src/runtime/executor/helpers/engines.dart
This commit is contained in:
commit
9e498fb575
|
@ -1,6 +1,6 @@
|
|||
# Run tasks with the dart SDK installed by default
|
||||
container:
|
||||
image: "google/dart:latest"
|
||||
container: # todo set back to latest
|
||||
image: "google/dart:dev"
|
||||
|
||||
# We're currently not running tests with coverage because the free cirrus containers run out of memory :(
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Files and directories created by pub
|
||||
.dart_tool/
|
||||
.packages
|
||||
# Remove the following pattern if you wish to check in your lock file
|
||||
pubspec.lock
|
||||
|
||||
# Conventional directory for build outputs
|
||||
build/
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
|
@ -0,0 +1,15 @@
|
|||
name: vm
|
||||
description: A sample command-line application.
|
||||
# version: 1.0.0
|
||||
# homepage: https://www.example.com
|
||||
# author: Simon Binder <oss@simonbinder.eu>
|
||||
|
||||
environment:
|
||||
sdk: '>=2.4.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
tests:
|
||||
path: ../tests
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.5.0
|
|
@ -0,0 +1,27 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:moor/moor_vm.dart';
|
||||
import 'package:tests/tests.dart';
|
||||
|
||||
import 'package:path/path.dart' show join;
|
||||
|
||||
class VmExecutor extends TestExecutor {
|
||||
static String fileName = 'moor-vm-tests-${DateTime.now().toIso8601String()}';
|
||||
final file = File(join(Directory.systemTemp.path, fileName));
|
||||
|
||||
@override
|
||||
QueryExecutor createExecutor() {
|
||||
return VMDatabase(file);
|
||||
}
|
||||
|
||||
@override
|
||||
Future deleteData() async {
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runAllTests(VmExecutor());
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/// A version of moor that runs on the Dart VM by integrating sqlite3 with
|
||||
/// ffi.
|
||||
@experimental
|
||||
library moor_vm;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'backends.dart';
|
||||
import 'moor.dart';
|
||||
|
||||
import 'src/vm/api/database.dart';
|
||||
|
||||
part 'src/vm/vm_database.dart';
|
|
@ -0,0 +1,161 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moor/src/vm/bindings/constants.dart';
|
||||
import 'package:moor/src/vm/bindings/types.dart' as types;
|
||||
import 'package:moor/src/vm/bindings/bindings.dart';
|
||||
import 'package:moor/src/vm/ffi/blob.dart';
|
||||
import 'package:moor/src/vm/ffi/utils.dart';
|
||||
|
||||
part 'errors.dart';
|
||||
part 'prepared_statement.dart';
|
||||
part 'result.dart';
|
||||
|
||||
const _openingFlags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE;
|
||||
|
||||
class Database {
|
||||
final Pointer<types.Database> _db;
|
||||
final List<PreparedStatement> _preparedStmt = [];
|
||||
bool _isClosed = false;
|
||||
|
||||
Database._(this._db);
|
||||
|
||||
/// Opens the [file] as a sqlite3 database. The file will be created if it
|
||||
/// doesn't exist.
|
||||
factory Database.openFile(File file) => Database.open(file.absolute.path);
|
||||
|
||||
/// Opens an in-memory sqlite3 database.
|
||||
factory Database.memory() => Database.open(':memory:');
|
||||
|
||||
/// Opens an sqlite3 database from a filename.
|
||||
factory Database.open(String fileName) {
|
||||
final dbOut = Pointer<Pointer<types.Database>>.allocate();
|
||||
final pathC = CBlob.allocateString(fileName);
|
||||
|
||||
final resultCode =
|
||||
bindings.sqlite3_open_v2(pathC, dbOut, _openingFlags, nullptr.cast());
|
||||
final dbPointer = dbOut.load<Pointer<types.Database>>();
|
||||
|
||||
dbOut.free();
|
||||
pathC.free();
|
||||
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
return Database._(dbPointer);
|
||||
} else {
|
||||
throw SqliteException._fromErrorCode(dbPointer, resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
void _ensureOpen() {
|
||||
if (_isClosed) {
|
||||
throw Exception('This database has already been closed');
|
||||
}
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
final code = bindings.sqlite3_close_v2(_db);
|
||||
SqliteException exception;
|
||||
if (code != Errors.SQLITE_OK) {
|
||||
exception = SqliteException._fromErrorCode(_db, code);
|
||||
}
|
||||
_isClosed = true;
|
||||
|
||||
for (var stmt in _preparedStmt) {
|
||||
stmt.close();
|
||||
}
|
||||
_db.free();
|
||||
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleStmtFinalized(PreparedStatement stmt) {
|
||||
if (!_isClosed) {
|
||||
_preparedStmt.remove(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the [sql] statement and ignores the result. Will throw if an
|
||||
/// error occurs while executing.
|
||||
void execute(String sql) {
|
||||
_ensureOpen();
|
||||
final sqlPtr = CBlob.allocateString(sql);
|
||||
final errorOut = Pointer<Pointer<CBlob>>.allocate();
|
||||
|
||||
final result =
|
||||
bindings.sqlite3_exec(_db, sqlPtr, nullptr, nullptr, errorOut);
|
||||
|
||||
sqlPtr.free();
|
||||
|
||||
final errorPtr = errorOut.load<Pointer<CBlob>>();
|
||||
errorOut.free();
|
||||
|
||||
String errorMsg;
|
||||
if (!isNullPointer(errorPtr)) {
|
||||
errorMsg = errorPtr.load<CBlob>().readString();
|
||||
// the message was allocated from sqlite, we need to free it
|
||||
bindings.sqlite3_free(errorPtr.cast());
|
||||
}
|
||||
|
||||
if (result != Errors.SQLITE_OK) {
|
||||
throw SqliteException(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepares the [sql] statement.
|
||||
PreparedStatement prepare(String sql) {
|
||||
_ensureOpen();
|
||||
|
||||
final stmtOut = Pointer<Pointer<types.Statement>>.allocate();
|
||||
final sqlPtr = CBlob.allocateString(sql);
|
||||
|
||||
final resultCode =
|
||||
bindings.sqlite3_prepare_v2(_db, sqlPtr, -1, stmtOut, nullptr.cast());
|
||||
sqlPtr.free();
|
||||
|
||||
final stmt = stmtOut.load<Pointer<types.Statement>>();
|
||||
stmtOut.free();
|
||||
|
||||
if (resultCode != Errors.SQLITE_OK) {
|
||||
// we don't need to worry about freeing the statement. If preparing the
|
||||
// statement was unsuccessful, stmtOut.load() will be null
|
||||
throw SqliteException._fromErrorCode(_db, resultCode);
|
||||
}
|
||||
|
||||
return PreparedStatement._(stmt, this);
|
||||
}
|
||||
|
||||
/// Get the application defined version of this database.
|
||||
int get userVersion {
|
||||
final stmt = prepare('PRAGMA user_version');
|
||||
final result = stmt.select();
|
||||
stmt.close();
|
||||
|
||||
return result.first.columnAt(0) as int;
|
||||
}
|
||||
|
||||
/// Update the application defined version of this database.
|
||||
set userVersion(int version) {
|
||||
execute('PRAGMA user_version = $version');
|
||||
}
|
||||
|
||||
/// Returns the amount of rows affected by the last INSERT, UPDATE or DELETE
|
||||
/// statement.
|
||||
int get updatedRows {
|
||||
_ensureOpen();
|
||||
return bindings.sqlite3_changes(_db);
|
||||
}
|
||||
|
||||
/// Returns the row-id of the last inserted row.
|
||||
int get lastInsertId {
|
||||
_ensureOpen();
|
||||
return bindings.sqlite3_last_insert_rowid(_db);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
part of 'database.dart';
|
||||
|
||||
class SqliteException implements Exception {
|
||||
final String message;
|
||||
final String explanation;
|
||||
|
||||
SqliteException(this.message, [this.explanation]);
|
||||
|
||||
factory SqliteException._fromErrorCode(Pointer<types.Database> db,
|
||||
[int code]) {
|
||||
// We don't need to free the pointer returned by sqlite3_errmsg: "Memory to
|
||||
// hold the error message string is managed internally. The application does
|
||||
// not need to worry about freeing the result."
|
||||
// https://www.sqlite.org/c3ref/errcode.html
|
||||
final dbMessage = bindings.sqlite3_errmsg(db).load<CBlob>().readString();
|
||||
|
||||
String explanation;
|
||||
if (code != null) {
|
||||
explanation = bindings.sqlite3_errstr(code).load<CBlob>().readString();
|
||||
}
|
||||
|
||||
return SqliteException(dbMessage, explanation);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (explanation == null) {
|
||||
return 'SqliteException: $message';
|
||||
} else {
|
||||
return 'SqliteException: $message, $explanation';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
part of 'database.dart';
|
||||
|
||||
class PreparedStatement {
|
||||
final Pointer<types.Statement> _stmt;
|
||||
final Database _db;
|
||||
bool _closed = false;
|
||||
|
||||
bool _bound = false;
|
||||
final List<Pointer> _allocatedWhileBinding = [];
|
||||
|
||||
PreparedStatement._(this._stmt, this._db);
|
||||
|
||||
void close() {
|
||||
if (!_closed) {
|
||||
_reset();
|
||||
bindings.sqlite3_finalize(_stmt);
|
||||
_db._handleStmtFinalized(this);
|
||||
}
|
||||
_closed = true;
|
||||
}
|
||||
|
||||
void _ensureNotFinalized() {
|
||||
if (_closed) {
|
||||
throw Exception('Tried to operate on a released prepared statement');
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes this prepared statement as a select statement. The returned rows
|
||||
/// will be returned.
|
||||
Result select([List<dynamic> params]) {
|
||||
_ensureNotFinalized();
|
||||
_reset();
|
||||
_bindParams(params);
|
||||
|
||||
final columnCount = bindings.sqlite3_column_count(_stmt);
|
||||
// not using a Map<String, int> for indexed because column names are not
|
||||
// guaranteed to be unique
|
||||
final names = List<String>(columnCount);
|
||||
final rows = <List<dynamic>>[];
|
||||
|
||||
for (var i = 0; i < columnCount; i++) {
|
||||
// name pointer doesn't need to be disposed, that happens when we finalize
|
||||
names[i] =
|
||||
bindings.sqlite3_column_name(_stmt, i).load<CBlob>().readString();
|
||||
}
|
||||
|
||||
while (_step() == Errors.SQLITE_ROW) {
|
||||
rows.add([for (var i = 0; i < columnCount; i++) _readValue(i)]);
|
||||
}
|
||||
|
||||
return Result(names, rows);
|
||||
}
|
||||
|
||||
dynamic _readValue(int index) {
|
||||
final type = bindings.sqlite3_column_type(_stmt, index);
|
||||
switch (type) {
|
||||
case Types.SQLITE_INTEGER:
|
||||
return bindings.sqlite3_column_int(_stmt, index);
|
||||
case Types.SQLITE_FLOAT:
|
||||
return bindings.sqlite3_column_double(_stmt, index);
|
||||
case Types.SQLITE_TEXT:
|
||||
return bindings
|
||||
.sqlite3_column_text(_stmt, index)
|
||||
.load<CBlob>()
|
||||
.readString();
|
||||
case Types.SQLITE_BLOB:
|
||||
final length = bindings.sqlite3_column_bytes(_stmt, index);
|
||||
return bindings
|
||||
.sqlite3_column_blob(_stmt, index)
|
||||
.load<CBlob>()
|
||||
.read(length);
|
||||
case Types.SQLITE_NULL:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes this prepared statement.
|
||||
void execute([List<dynamic> params]) {
|
||||
_ensureNotFinalized();
|
||||
_reset();
|
||||
_bindParams(params);
|
||||
|
||||
final result = _step();
|
||||
|
||||
if (result != Errors.SQLITE_OK && result != Errors.SQLITE_DONE) {
|
||||
throw SqliteException._fromErrorCode(_db._db, result);
|
||||
}
|
||||
}
|
||||
|
||||
void _reset() {
|
||||
if (_bound) {
|
||||
bindings.sqlite3_reset(_stmt);
|
||||
_bound = false;
|
||||
}
|
||||
for (var pointer in _allocatedWhileBinding) {
|
||||
pointer.free();
|
||||
}
|
||||
_allocatedWhileBinding.clear();
|
||||
}
|
||||
|
||||
void _bindParams(List<dynamic> params) {
|
||||
if (params != null && params.isNotEmpty) {
|
||||
// variables in sqlite are 1-indexed
|
||||
for (var i = 1; i <= params.length; i++) {
|
||||
final param = params[i - 1];
|
||||
|
||||
if (param == null) {
|
||||
bindings.sqlite3_bind_null(_stmt, i);
|
||||
} else if (param is int) {
|
||||
bindings.sqlite3_bind_int(_stmt, i, param);
|
||||
} else if (param is num) {
|
||||
bindings.sqlite3_bind_double(_stmt, i, param.toDouble());
|
||||
} else if (param is String) {
|
||||
final ptr = CBlob.allocateString(param);
|
||||
_allocatedWhileBinding.add(ptr);
|
||||
|
||||
bindings.sqlite3_bind_text(_stmt, i, ptr, -1, nullptr);
|
||||
} else if (param is Uint8List) {
|
||||
// todo we just have a null pointer param.isEmpty. I guess we have
|
||||
// to use sqlite3_bind_zeroblob for that?
|
||||
final ptr = CBlob.allocate(param);
|
||||
_allocatedWhileBinding.add(ptr);
|
||||
|
||||
bindings.sqlite3_bind_blob(_stmt, i, ptr, param.length, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
_bound = true;
|
||||
}
|
||||
|
||||
int _step() => bindings.sqlite3_step(_stmt);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
part of 'database.dart';
|
||||
|
||||
/// Stores the result of a select statement.
|
||||
class Result extends Iterable<Row> {
|
||||
final List<String> columnNames;
|
||||
// a result set can have multiple columns with the same name, but that's rare
|
||||
// and users usually use a name as index. So we cache that for O(1) lookups
|
||||
Map<String, int> _calculatedIndexes;
|
||||
final List<List<dynamic>> rows;
|
||||
|
||||
Result(this.columnNames, this.rows) {
|
||||
_calculatedIndexes = {
|
||||
for (var column in columnNames) column: columnNames.lastIndexOf(column),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Iterator<Row> get iterator => _ResultIterator(this);
|
||||
}
|
||||
|
||||
/// Stores a single row in the result of a select statement.
|
||||
class Row extends MapMixin<String, dynamic>
|
||||
with UnmodifiableMapMixin<String, dynamic> {
|
||||
final Result _result;
|
||||
final int _rowIndex;
|
||||
|
||||
Row._(this._result, this._rowIndex);
|
||||
|
||||
/// Returns the value stored in the [i]-th column in this row (zero-indexed).
|
||||
dynamic columnAt(int i) {
|
||||
return _result.rows[_rowIndex][i];
|
||||
}
|
||||
|
||||
@override
|
||||
operator [](Object key) {
|
||||
if (key is! String) return null;
|
||||
|
||||
final index = _result._calculatedIndexes[key];
|
||||
if (index == null) return null;
|
||||
|
||||
return columnAt(index);
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<String> get keys => _result.columnNames;
|
||||
}
|
||||
|
||||
class _ResultIterator extends Iterator<Row> {
|
||||
final Result result;
|
||||
int index = -1;
|
||||
|
||||
_ResultIterator(this.result);
|
||||
|
||||
@override
|
||||
Row get current => Row._(result, index);
|
||||
|
||||
@override
|
||||
bool moveNext() {
|
||||
index++;
|
||||
return index < result.rows.length;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import '../ffi/blob.dart';
|
||||
import '../ffi/open_platform_specific.dart';
|
||||
|
||||
import 'signatures.dart';
|
||||
import 'types.dart';
|
||||
|
||||
// ignore_for_file: comment_references, non_constant_identifier_names
|
||||
|
||||
class _SQLiteBindings {
|
||||
DynamicLibrary sqlite;
|
||||
|
||||
int Function(Pointer<CBlob> filename, Pointer<Pointer<Database>> databaseOut,
|
||||
int flags, Pointer<CBlob> vfs) sqlite3_open_v2;
|
||||
|
||||
int Function(Pointer<Database> database) sqlite3_close_v2;
|
||||
void Function(Pointer<Void> ptr) sqlite3_free;
|
||||
|
||||
int Function(
|
||||
Pointer<Database> database,
|
||||
Pointer<CBlob> query,
|
||||
int nbytes,
|
||||
Pointer<Pointer<Statement>> statementOut,
|
||||
Pointer<Pointer<CBlob>> tail) sqlite3_prepare_v2;
|
||||
|
||||
int Function(
|
||||
Pointer<Database> database,
|
||||
Pointer<CBlob> query,
|
||||
Pointer<Void> callback,
|
||||
Pointer<Void> cbFirstArg,
|
||||
Pointer<Pointer<CBlob>> errorMsgOut,
|
||||
) sqlite3_exec;
|
||||
|
||||
int Function(Pointer<Statement> statement) sqlite3_step;
|
||||
|
||||
int Function(Pointer<Statement> statement) sqlite3_reset;
|
||||
|
||||
int Function(Pointer<Statement> statement) sqlite3_finalize;
|
||||
|
||||
int Function(Pointer<Statement> statement) sqlite3_column_count;
|
||||
|
||||
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_name;
|
||||
|
||||
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_decltype;
|
||||
|
||||
int Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_type;
|
||||
|
||||
Pointer<Value> Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_value;
|
||||
double Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_double;
|
||||
int Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_int;
|
||||
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_text;
|
||||
Pointer<CBlob> Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_blob;
|
||||
|
||||
/// Returns the amount of bytes to read when using [sqlite3_column_blob].
|
||||
int Function(Pointer<Statement> statement, int columnIndex)
|
||||
sqlite3_column_bytes;
|
||||
|
||||
int Function(Pointer<Database> db) sqlite3_changes;
|
||||
int Function(Pointer<Database> db) sqlite3_last_insert_rowid;
|
||||
|
||||
Pointer<CBlob> Function(int code) sqlite3_errstr;
|
||||
Pointer<CBlob> Function(Pointer<Database> database) sqlite3_errmsg;
|
||||
|
||||
int Function(Pointer<Statement> statement, int columnIndex, double value)
|
||||
sqlite3_bind_double;
|
||||
int Function(Pointer<Statement> statement, int columnIndex, int value)
|
||||
sqlite3_bind_int;
|
||||
int Function(
|
||||
Pointer<Statement> statement,
|
||||
int columnIndex,
|
||||
Pointer<CBlob> value,
|
||||
int minusOne,
|
||||
Pointer<Void> disposeCb) sqlite3_bind_text;
|
||||
int Function(
|
||||
Pointer<Statement> statement,
|
||||
int columnIndex,
|
||||
Pointer<CBlob> value,
|
||||
int length,
|
||||
Pointer<Void> disposeCb) sqlite3_bind_blob;
|
||||
int Function(Pointer<Statement> statement, int columnIndex) sqlite3_bind_null;
|
||||
|
||||
_SQLiteBindings() {
|
||||
sqlite = dlopenPlatformSpecific('sqlite3');
|
||||
|
||||
sqlite3_bind_double = sqlite
|
||||
.lookup<NativeFunction<sqlite3_bind_double_native>>(
|
||||
'sqlite3_bind_double')
|
||||
.asFunction();
|
||||
sqlite3_bind_int = sqlite
|
||||
.lookup<NativeFunction<sqlite3_bind_int_native>>('sqlite3_bind_int')
|
||||
.asFunction();
|
||||
sqlite3_bind_text = sqlite
|
||||
.lookup<NativeFunction<sqlite3_bind_text_native>>('sqlite3_bind_text')
|
||||
.asFunction();
|
||||
sqlite3_bind_blob = sqlite
|
||||
.lookup<NativeFunction<sqlite3_bind_blob_native>>('sqlite3_bind_blob')
|
||||
.asFunction();
|
||||
sqlite3_bind_null = sqlite
|
||||
.lookup<NativeFunction<sqlite3_bind_null_native>>('sqlite3_bind_null')
|
||||
.asFunction();
|
||||
sqlite3_open_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_open_v2_native_t>>('sqlite3_open_v2')
|
||||
.asFunction();
|
||||
sqlite3_close_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_close_v2_native_t>>('sqlite3_close_v2')
|
||||
.asFunction();
|
||||
sqlite3_free = sqlite
|
||||
.lookup<NativeFunction<sqlite3_free_native>>('sqlite3_free')
|
||||
.asFunction();
|
||||
sqlite3_prepare_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
|
||||
'sqlite3_prepare_v2')
|
||||
.asFunction();
|
||||
sqlite3_exec = sqlite
|
||||
.lookup<NativeFunction<sqlite3_exec_native>>('sqlite3_exec')
|
||||
.asFunction();
|
||||
sqlite3_step = sqlite
|
||||
.lookup<NativeFunction<sqlite3_step_native_t>>('sqlite3_step')
|
||||
.asFunction();
|
||||
sqlite3_reset = sqlite
|
||||
.lookup<NativeFunction<sqlite3_reset_native_t>>('sqlite3_reset')
|
||||
.asFunction();
|
||||
sqlite3_changes = sqlite
|
||||
.lookup<NativeFunction<sqlite3_changes_native>>('sqlite3_changes')
|
||||
.asFunction();
|
||||
sqlite3_last_insert_rowid = sqlite
|
||||
.lookup<NativeFunction<sqlite3_last_insert_rowid_native>>(
|
||||
'sqlite3_last_insert_rowid')
|
||||
.asFunction();
|
||||
sqlite3_finalize = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>('sqlite3_finalize')
|
||||
.asFunction();
|
||||
sqlite3_errstr = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errstr_native_t>>('sqlite3_errstr')
|
||||
.asFunction();
|
||||
sqlite3_errmsg = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errmsg_native_t>>('sqlite3_errmsg')
|
||||
.asFunction();
|
||||
sqlite3_column_count = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_count_native_t>>(
|
||||
'sqlite3_column_count')
|
||||
.asFunction();
|
||||
sqlite3_column_name = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_name_native_t>>(
|
||||
'sqlite3_column_name')
|
||||
.asFunction();
|
||||
sqlite3_column_decltype = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_decltype_native_t>>(
|
||||
'sqlite3_column_decltype')
|
||||
.asFunction();
|
||||
sqlite3_column_type = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_type_native_t>>(
|
||||
'sqlite3_column_type')
|
||||
.asFunction();
|
||||
sqlite3_column_value = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_value_native_t>>(
|
||||
'sqlite3_column_value')
|
||||
.asFunction();
|
||||
sqlite3_column_double = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_double_native_t>>(
|
||||
'sqlite3_column_double')
|
||||
.asFunction();
|
||||
sqlite3_column_int = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_int_native_t>>(
|
||||
'sqlite3_column_int')
|
||||
.asFunction();
|
||||
sqlite3_column_text = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_text_native_t>>(
|
||||
'sqlite3_column_text')
|
||||
.asFunction();
|
||||
sqlite3_column_blob = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_blob_native_t>>(
|
||||
'sqlite3_column_blob')
|
||||
.asFunction();
|
||||
sqlite3_column_bytes = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_bytes_native_t>>(
|
||||
'sqlite3_column_bytes')
|
||||
.asFunction();
|
||||
}
|
||||
}
|
||||
|
||||
_SQLiteBindings _cachedBindings;
|
||||
_SQLiteBindings get bindings => _cachedBindings ??= _SQLiteBindings();
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: constant_identifier_names
|
||||
|
||||
/// Result Codes
|
||||
///
|
||||
/// Many SQLite functions return an integer result code from the set shown
|
||||
/// here in order to indicates success or failure.
|
||||
///
|
||||
/// New error codes may be added in future versions of SQLite.
|
||||
///
|
||||
/// See also: SQLITE_IOERR_READ | extended result codes,
|
||||
/// sqlite3_vtab_on_conflict() SQLITE_ROLLBACK | result codes.
|
||||
class Errors {
|
||||
/// Successful result
|
||||
static const int SQLITE_OK = 0;
|
||||
|
||||
/// Generic error
|
||||
static const int SQLITE_ERROR = 1;
|
||||
|
||||
/// Internal logic error in SQLite
|
||||
static const int SQLITE_INTERNAL = 2;
|
||||
|
||||
/// Access permission denied
|
||||
static const int SQLITE_PERM = 3;
|
||||
|
||||
/// Callback routine requested an abort
|
||||
static const int SQLITE_ABORT = 4;
|
||||
|
||||
/// The database file is locked
|
||||
static const int SQLITE_BUSY = 5;
|
||||
|
||||
/// A table in the database is locked
|
||||
static const int SQLITE_LOCKED = 6;
|
||||
|
||||
/// A malloc() failed
|
||||
static const int SQLITE_NOMEM = 7;
|
||||
|
||||
/// Attempt to write a readonly database
|
||||
static const int SQLITE_READONLY = 8;
|
||||
|
||||
/// Operation terminated by sqlite3_interrupt()
|
||||
static const int SQLITE_INTERRUPT = 9;
|
||||
|
||||
/// Some kind of disk I/O error occurred
|
||||
static const int SQLITE_IOERR = 10;
|
||||
|
||||
/// The database disk image is malformed
|
||||
static const int SQLITE_CORRUPT = 11;
|
||||
|
||||
/// Unknown opcode in sqlite3_file_control()
|
||||
static const int SQLITE_NOTFOUND = 12;
|
||||
|
||||
/// Insertion failed because database is full
|
||||
static const int SQLITE_FULL = 13;
|
||||
|
||||
/// Unable to open the database file
|
||||
static const int SQLITE_CANTOPEN = 14;
|
||||
|
||||
/// Database lock protocol error
|
||||
static const int SQLITE_PROTOCOL = 15;
|
||||
|
||||
/// Internal use only
|
||||
static const int SQLITE_EMPTY = 16;
|
||||
|
||||
/// The database schema changed
|
||||
static const int SQLITE_SCHEMA = 17;
|
||||
|
||||
/// String or BLOB exceeds size limit
|
||||
static const int SQLITE_TOOBIG = 18;
|
||||
|
||||
/// Abort due to constraint violation
|
||||
static const int SQLITE_CONSTRAINT = 19;
|
||||
|
||||
/// Data type mismatch
|
||||
static const int SQLITE_MISMATCH = 20;
|
||||
|
||||
/// Library used incorrectly
|
||||
static const int SQLITE_MISUSE = 21;
|
||||
|
||||
/// Uses OS features not supported on host
|
||||
static const int SQLITE_NOLFS = 22;
|
||||
|
||||
/// Authorization denied
|
||||
static const int SQLITE_AUTH = 23;
|
||||
|
||||
/// Not used
|
||||
static const int SQLITE_FORMAT = 24;
|
||||
|
||||
/// 2nd parameter to sqlite3_bind out of range
|
||||
static const int SQLITE_RANGE = 25;
|
||||
|
||||
/// File opened that is not a database file
|
||||
static const int SQLITE_NOTADB = 26;
|
||||
|
||||
/// Notifications from sqlite3_log()
|
||||
static const int SQLITE_NOTICE = 27;
|
||||
|
||||
/// Warnings from sqlite3_log()
|
||||
static const int SQLITE_WARNING = 28;
|
||||
|
||||
/// sqlite3_step() has another row ready
|
||||
static const int SQLITE_ROW = 100;
|
||||
|
||||
/// sqlite3_step() has finished executing
|
||||
static const int SQLITE_DONE = 101;
|
||||
}
|
||||
|
||||
/// Flags For File Open Operations
|
||||
///
|
||||
/// These bit values are intended for use in the
|
||||
/// 3rd parameter to the [sqlite3_open_v2()] interface and
|
||||
/// in the 4th parameter to the `sqlite3_vfs.xOpen` method.
|
||||
class Flags {
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_READONLY = 0x00000001;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_READWRITE = 0x00000002;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_CREATE = 0x00000004;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_EXCLUSIVE = 0x00000010;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_AUTOPROXY = 0x00000020;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_URI = 0x00000040;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_MEMORY = 0x00000080;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MAIN_DB = 0x00000100;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TEMP_DB = 0x00000200;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_SUBJOURNAL = 0x00002000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MASTER_JOURNAL = 0x00004000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_NOMUTEX = 0x00008000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_FULLMUTEX = 0x00010000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_SHAREDCACHE = 0x00020000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_PRIVATECACHE = 0x00040000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_WAL = 0x00080000;
|
||||
}
|
||||
|
||||
class Types {
|
||||
static const int SQLITE_INTEGER = 1;
|
||||
static const int SQLITE_FLOAT = 2;
|
||||
static const int SQLITE_TEXT = 3;
|
||||
static const int SQLITE_BLOB = 4;
|
||||
static const int SQLITE_NULL = 5;
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import '../ffi/blob.dart';
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
typedef sqlite3_open_v2_native_t = Int32 Function(Pointer<CBlob> filename,
|
||||
Pointer<Pointer<Database>> ppDb, Int32 flags, Pointer<CBlob> vfs);
|
||||
|
||||
typedef sqlite3_close_v2_native_t = Int32 Function(Pointer<Database> database);
|
||||
|
||||
typedef sqlite3_free_native = Void Function(Pointer<Void> pointer);
|
||||
|
||||
typedef sqlite3_prepare_v2_native_t = Int32 Function(
|
||||
Pointer<Database> database,
|
||||
Pointer<CBlob> query,
|
||||
Int32 nbytes,
|
||||
Pointer<Pointer<Statement>> statementOut,
|
||||
Pointer<Pointer<CBlob>> tail);
|
||||
|
||||
typedef sqlite3_exec_native = Int32 Function(
|
||||
Pointer<Database> database,
|
||||
Pointer<CBlob> query,
|
||||
Pointer<Void> callback,
|
||||
Pointer<Void> firstCbArg,
|
||||
Pointer<Pointer<CBlob>> errorOut);
|
||||
|
||||
typedef sqlite3_step_native_t = Int32 Function(Pointer<Statement> statement);
|
||||
|
||||
typedef sqlite3_reset_native_t = Int32 Function(Pointer<Statement> statement);
|
||||
|
||||
typedef sqlite3_finalize_native_t = Int32 Function(
|
||||
Pointer<Statement> statement);
|
||||
|
||||
typedef sqlite3_errstr_native_t = Pointer<CBlob> Function(Int32 error);
|
||||
|
||||
typedef sqlite3_errmsg_native_t = Pointer<CBlob> Function(
|
||||
Pointer<Database> database);
|
||||
|
||||
typedef sqlite3_column_count_native_t = Int32 Function(
|
||||
Pointer<Statement> statement);
|
||||
|
||||
typedef sqlite3_column_name_native_t = Pointer<CBlob> Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_decltype_native_t = Pointer<CBlob> Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_type_native_t = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_value_native_t = Pointer<Value> Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_double_native_t = Double Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_int_native_t = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_text_native_t = Pointer<CBlob> Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_blob_native_t = Pointer<CBlob> Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_bytes_native_t = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_changes_native = Int32 Function(Pointer<Database> database);
|
||||
typedef sqlite3_last_insert_rowid_native = Int64 Function(
|
||||
Pointer<Database> database);
|
||||
|
||||
typedef sqlite3_bind_double_native = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex, Double value);
|
||||
typedef sqlite3_bind_int_native = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex, Int32 value);
|
||||
typedef sqlite3_bind_text_native = Int32 Function(
|
||||
Pointer<Statement> statement,
|
||||
Int32 columnIndex,
|
||||
Pointer<CBlob> value,
|
||||
Int32 length,
|
||||
Pointer<Void> callback);
|
||||
typedef sqlite3_bind_blob_native = Int32 Function(
|
||||
Pointer<Statement> statement,
|
||||
Int32 columnIndex,
|
||||
Pointer<CBlob> value,
|
||||
Int32 length,
|
||||
Pointer<Void> callback);
|
||||
typedef sqlite3_bind_null_native = Int32 Function(
|
||||
Pointer<Statement> statement, Int32 columnIndex);
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
// ignore_for_file: comment_references
|
||||
|
||||
/// Database Connection Handle
|
||||
///
|
||||
/// Each open SQLite database is represented by a pointer to an instance of
|
||||
/// the opaque structure named "sqlite3". It is useful to think of an sqlite3
|
||||
/// pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
|
||||
/// [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()]
|
||||
/// is its destructor. There are many other interfaces (such as
|
||||
/// [sqlite3_prepare_v2()], [sqlite3_create_function()], and
|
||||
/// [sqlite3_busy_timeout()] to name but three) that are methods on an
|
||||
class Database extends Struct<Database> {}
|
||||
|
||||
/// SQL Statement Object
|
||||
///
|
||||
/// An instance of this object represents a single SQL statement.
|
||||
/// This object is variously known as a "prepared statement" or a
|
||||
/// "compiled SQL statement" or simply as a "statement".
|
||||
///
|
||||
/// The life of a statement object goes something like this:
|
||||
///
|
||||
/// <ol>
|
||||
/// <li> Create the object using [sqlite3_prepare_v2()] or a related
|
||||
/// function.
|
||||
/// <li> Bind values to [host parameters] using the sqlite3_bind_*()
|
||||
/// interfaces.
|
||||
/// <li> Run the SQL by calling [sqlite3_step()] one or more times.
|
||||
/// <li> Reset the statement using [sqlite3_reset()] then go back
|
||||
/// to step 2. Do this zero or more times.
|
||||
/// <li> Destroy the object using [sqlite3_finalize()].
|
||||
/// </ol>
|
||||
///
|
||||
/// Refer to documentation on individual methods above for additional
|
||||
/// information.
|
||||
class Statement extends Struct<Statement> {}
|
||||
|
||||
/// Dynamically Typed Value Object
|
||||
///
|
||||
/// SQLite uses the sqlite3_value object to represent all values
|
||||
/// that can be stored in a database table. SQLite uses dynamic typing
|
||||
/// for the values it stores. ^Values stored in sqlite3_value objects
|
||||
/// can be integers, floating point values, strings, BLOBs, or NULL.
|
||||
///
|
||||
/// An sqlite3_value object may be either "protected" or "unprotected".
|
||||
/// Some interfaces require a protected sqlite3_value. Other interfaces
|
||||
/// will accept either a protected or an unprotected sqlite3_value.
|
||||
/// Every interface that accepts sqlite3_value arguments specifies
|
||||
/// whether or not it requires a protected sqlite3_value.
|
||||
///
|
||||
/// The terms "protected" and "unprotected" refer to whether or not
|
||||
/// a mutex is held. An internal mutex is held for a protected
|
||||
/// sqlite3_value object but no mutex is held for an unprotected
|
||||
/// sqlite3_value object. If SQLite is compiled to be single-threaded
|
||||
/// (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0)
|
||||
/// or if SQLite is run in one of reduced mutex modes
|
||||
/// [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD]
|
||||
/// then there is no distinction between protected and unprotected
|
||||
/// sqlite3_value objects and they can be used interchangeably. However,
|
||||
/// for maximum code portability it is recommended that applications
|
||||
/// still make the distinction between protected and unprotected
|
||||
/// sqlite3_value objects even when not strictly required.
|
||||
///
|
||||
/// ^The sqlite3_value objects that are passed as parameters into the
|
||||
/// implementation of [application-defined SQL functions] are protected.
|
||||
/// ^The sqlite3_value object returned by
|
||||
/// [sqlite3_column_value()] is unprotected.
|
||||
/// Unprotected sqlite3_value objects may only be used with
|
||||
/// [sqlite3_result_value()] and [sqlite3_bind_value()].
|
||||
/// The [sqlite3_value_blob | sqlite3_value_type()] family of
|
||||
/// interfaces require protected sqlite3_value objects.
|
||||
class Value extends Struct<Value> {}
|
|
@ -0,0 +1,54 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:moor/src/vm/ffi/utils.dart';
|
||||
|
||||
/// Pointer to arbitrary blobs in C.
|
||||
class CBlob extends Struct<CBlob> {
|
||||
@Uint8()
|
||||
int data;
|
||||
|
||||
static Pointer<CBlob> allocate(Uint8List blob) {
|
||||
final str = Pointer<CBlob>.allocate(count: blob.length);
|
||||
for (var i = 0; i < blob.length; i++) {
|
||||
str.elementAt(i).load<CBlob>().data = blob[i];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/// Allocates a 0-terminated string, encoded as utf8 and read from the
|
||||
/// [string].
|
||||
static Pointer<CBlob> allocateString(String string) {
|
||||
final encoded = utf8.encode(string);
|
||||
final data = Uint8List(encoded.length + 1) // already filled with zeroes
|
||||
..setAll(0, encoded);
|
||||
return CBlob.allocate(data);
|
||||
}
|
||||
|
||||
/// Reads [bytesToRead] bytes from the current position.
|
||||
Uint8List read(int bytesToRead) {
|
||||
assert(bytesToRead >= 0);
|
||||
final str = addressOf;
|
||||
if (isNullPointer(str)) return null;
|
||||
|
||||
final blob = Uint8List(bytesToRead);
|
||||
for (var i = 0; i < bytesToRead; ++i) {
|
||||
blob[i] = str.elementAt(i).load<CBlob>().data;
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
|
||||
/// Reads a 0-terminated string, decoded with utf8.
|
||||
String readString() {
|
||||
final str = addressOf;
|
||||
if (isNullPointer(str)) return null;
|
||||
|
||||
var len = 0;
|
||||
while (str.elementAt(++len).load<CBlob>().data != 0) {}
|
||||
|
||||
final units = read(len);
|
||||
return utf8.decode(units);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
String _platformPath(String name, {String path}) {
|
||||
final resolvedPath = path ?? '';
|
||||
|
||||
if (Platform.isLinux || Platform.isAndroid) {
|
||||
return '${resolvedPath}lib$name.so';
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
return '${resolvedPath}lib$name.dylib';
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
return '$resolvedPath$name.dll';
|
||||
}
|
||||
|
||||
throw UnsupportedError('Platform not implemented');
|
||||
}
|
||||
|
||||
DynamicLibrary dlopenPlatformSpecific(String name, {String path}) {
|
||||
final resolvedPath = _platformPath(name, path: path);
|
||||
return DynamicLibrary.open(resolvedPath);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
bool isNullPointer<T extends NativeType>(Pointer<T> ptr) => ptr == nullptr;
|
|
@ -0,0 +1,113 @@
|
|||
part of 'package:moor/moor_vm.dart';
|
||||
|
||||
/// A moor database that runs on the Dart VM.
|
||||
class VMDatabase extends DelegatedDatabase {
|
||||
VMDatabase._(DatabaseDelegate delegate, bool logStatements)
|
||||
: super(delegate, isSequential: true, logStatements: logStatements);
|
||||
|
||||
/// Creates a database that will store its result in the [file], creating it
|
||||
/// if it doesn't exist.
|
||||
factory VMDatabase(File file, {bool logStatements = false}) {
|
||||
return VMDatabase._(_VmDelegate(file), logStatements);
|
||||
}
|
||||
|
||||
/// Creates a database won't persist its changes on disk.
|
||||
factory VMDatabase.memory({bool logStatements = false}) {
|
||||
return VMDatabase._(_VmDelegate(null), logStatements);
|
||||
}
|
||||
}
|
||||
|
||||
class _VmDelegate extends DatabaseDelegate {
|
||||
Database _db;
|
||||
|
||||
final File file;
|
||||
|
||||
_VmDelegate(this.file);
|
||||
|
||||
@override
|
||||
final TransactionDelegate transactionDelegate = const NoTransactionDelegate();
|
||||
|
||||
@override
|
||||
DbVersionDelegate versionDelegate;
|
||||
|
||||
@override
|
||||
Future<bool> get isOpen => Future.value(_db != null);
|
||||
|
||||
@override
|
||||
Future<void> open([GeneratedDatabase db]) {
|
||||
if (file != null) {
|
||||
_db = Database.openFile(file);
|
||||
} else {
|
||||
_db = Database.memory();
|
||||
}
|
||||
versionDelegate = _VmVersionDelegate(_db);
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> runBatched(List<BatchedStatement> statements) {
|
||||
for (var stmt in statements) {
|
||||
final prepared = _db.prepare(stmt.sql);
|
||||
|
||||
for (var boundVars in stmt.variables) {
|
||||
prepared.execute(boundVars);
|
||||
}
|
||||
|
||||
prepared.close();
|
||||
}
|
||||
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
void _runWithArgs(String statement, List<dynamic> args) {
|
||||
if (args.isEmpty) {
|
||||
_db.execute(statement);
|
||||
} else {
|
||||
_db.prepare(statement)
|
||||
..execute(args)
|
||||
..close();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> runCustom(String statement, List args) {
|
||||
_runWithArgs(statement, args);
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> runInsert(String statement, List args) {
|
||||
_runWithArgs(statement, args);
|
||||
return Future.value(_db.lastInsertId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> runUpdate(String statement, List args) {
|
||||
_runWithArgs(statement, args);
|
||||
return Future.value(_db.updatedRows);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<QueryResult> runSelect(String statement, List args) {
|
||||
final stmt = _db.prepare(statement);
|
||||
final result = stmt.select(args);
|
||||
stmt.close();
|
||||
|
||||
return Future.value(QueryResult(result.columnNames, result.rows));
|
||||
}
|
||||
}
|
||||
|
||||
class _VmVersionDelegate extends DynamicVersionDelegate {
|
||||
final Database database;
|
||||
|
||||
_VmVersionDelegate(this.database);
|
||||
|
||||
@override
|
||||
Future<int> get schemaVersion => Future.value(database.userVersion);
|
||||
|
||||
@override
|
||||
Future<void> setSchemaVersion(int version) {
|
||||
database.userVersion = version;
|
||||
return Future.value();
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ authors:
|
|||
maintainer: Simon Binder (@simolus3)
|
||||
|
||||
environment:
|
||||
sdk: '>=2.2.2 <3.0.0'
|
||||
sdk: '>=2.5.0-dev <3.0.0'
|
||||
|
||||
dependencies:
|
||||
meta: ^1.0.0
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test_api/test_api.dart';
|
||||
import 'package:moor/moor_vm.dart';
|
||||
|
||||
import '../data/tables/todos.dart';
|
||||
|
||||
TodoDb db;
|
||||
|
||||
void main() {
|
||||
test('CRUD integration test', () async {
|
||||
db = TodoDb(VMDatabase.memory(logStatements: false));
|
||||
|
||||
// write some dummy data
|
||||
await insertCategory();
|
||||
await insertUser();
|
||||
await insertTodos();
|
||||
|
||||
await db.into(db.sharedTodos).insert(SharedTodo(todo: 2, user: 1));
|
||||
|
||||
// test select statements
|
||||
final forUser = (await db.someDao.todosForUser(1)).single;
|
||||
expect(forUser.title, 'Another entry');
|
||||
|
||||
// test delete statements
|
||||
await db.deleteTodoById(2);
|
||||
final queryAgain = await db.someDao.todosForUser(1);
|
||||
expect(queryAgain, isEmpty);
|
||||
|
||||
// test update statements
|
||||
await (db.update(db.todosTable)..where((t) => t.id.equals(1)))
|
||||
.write(const TodosTableCompanion(content: Value('Updated content')));
|
||||
final readUpdated = await db.select(db.todosTable).getSingle();
|
||||
expect(readUpdated.content, 'Updated content');
|
||||
});
|
||||
|
||||
test('Transactions test', () async {
|
||||
db = TodoDb(VMDatabase.memory(logStatements: false));
|
||||
|
||||
final completedOperations = StreamController<String>();
|
||||
|
||||
unawaited(db.transaction((_) async {
|
||||
await insertCategory();
|
||||
completedOperations.add('transaction');
|
||||
await pumpEventQueue();
|
||||
}));
|
||||
|
||||
unawaited(insertUser().then((_) {
|
||||
completedOperations.add('regular');
|
||||
}));
|
||||
|
||||
await expectLater(
|
||||
completedOperations.stream, emitsInOrder(['transaction', 'regular']));
|
||||
|
||||
// call .getSingle to verify both rows have been written
|
||||
await db.select(db.users).getSingle();
|
||||
await db.select(db.categories).getSingle();
|
||||
});
|
||||
}
|
||||
|
||||
Future insertCategory() async {
|
||||
final forInsert = const CategoriesCompanion(description: Value('Work'));
|
||||
final row = Category(id: 1, description: 'Work');
|
||||
|
||||
final id = await db.into(db.categories).insert(forInsert);
|
||||
expect(id, equals(1));
|
||||
|
||||
final loaded = await db.select(db.categories).getSingle();
|
||||
expect(loaded, equals(row));
|
||||
}
|
||||
|
||||
Future insertUser() async {
|
||||
final profilePic = Uint8List.fromList([1, 2, 3, 4, 5, 6]);
|
||||
final forInsert = UsersCompanion(
|
||||
name: const Value('Dashy McDashface'),
|
||||
isAwesome: const Value(true),
|
||||
profilePicture: Value(profilePic),
|
||||
);
|
||||
|
||||
final id = await db.into(db.users).insert(forInsert);
|
||||
expect(id, equals(1));
|
||||
|
||||
final user = await db.select(db.users).getSingle();
|
||||
expect(user.id, equals(1));
|
||||
expect(user.name, equals('Dashy McDashface'));
|
||||
expect(user.isAwesome, isTrue);
|
||||
expect(user.profilePicture, profilePic);
|
||||
}
|
||||
|
||||
Future insertTodos() async {
|
||||
await db.into(db.todosTable).insertAll([
|
||||
TodosTableCompanion(
|
||||
title: const Value('A first entry'),
|
||||
content: const Value('Some content I guess'),
|
||||
targetDate: Value(DateTime(2019)),
|
||||
),
|
||||
const TodosTableCompanion(
|
||||
title: Value('Another entry'),
|
||||
content: Value('this is a really creative test case'),
|
||||
category: Value(1), // "Work"
|
||||
),
|
||||
]);
|
||||
}
|
Loading…
Reference in New Issue