Use our own sqlite api instead of Dart example

This commit is contained in:
Simon Binder 2019-07-20 17:20:47 +02:00
parent 5dde293015
commit e00f7bfa29
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
33 changed files with 722 additions and 1883 deletions

11
moor/example/test_vm.dart Normal file
View File

@ -0,0 +1,11 @@
import 'dart:io';
import 'package:moor/moor_vm.dart';
void main() async {
final executor = VMDatabase(File('test.db'), logStatements: true);
await executor.doWhenOpened((_) async {
await executor.close();
});
}

View File

@ -6,7 +6,8 @@ library moor_vm;
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:sqlite3_ffi/sqlite.dart';
import 'moor.dart';
import 'src/vm/api/database.dart';
part 'src/vm/vm_database.dart';

View File

@ -44,6 +44,12 @@ abstract class QueryExecutor {
/// Starts a [TransactionExecutor].
TransactionExecutor beginTransaction();
/// Closes this database connection. After this future completes, all further
/// calls to this executor should fail.
Future<void> close() {
return Future.value();
}
}
/// A statement that should be executed in a batch. Used internally by moor.

View File

@ -0,0 +1,162 @@
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';
import 'package:moor/src/vm/bindings/bindings.dart';
import 'package:moor/src/vm/ffi/blob.dart';
part 'errors.dart';
part 'prepared_statement.dart';
part 'result.dart';
const _openingFlags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE;
final _nullPtr = fromAddress(0);
class Database {
final DatabasePointer _db;
final List<PreparedStatement> _preparedStmt = [];
bool _isClosed;
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 = allocate<DatabasePointer>();
final pathC = CString.allocate(fileName);
final resultCode =
bindings.sqlite3_open_v2(pathC, dbOut, _openingFlags, _nullPtr.cast());
final dbPointer = dbOut.load<DatabasePointer>();
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 = CString.allocate(sql);
final errorOut = allocate<CString>();
final result = bindings.sqlite3_exec(
_db, sqlPtr, _nullPtr.cast(), _nullPtr.cast(), errorOut);
sqlPtr.free();
final errorPtr = errorOut.load<Pointer>();
errorOut.free();
String errorMsg;
if (errorPtr.address != 0) {
errorMsg = CString.fromC(errorPtr.cast());
// 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 = allocate<StatementPointer>();
final sqlPtr = CString.allocate(sql);
final resultCode =
bindings.sqlite3_prepare_v2(_db, sqlPtr, -1, stmtOut, _nullPtr.cast());
sqlPtr.free();
final stmt = stmtOut.load<StatementPointer>();
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 the null pointer
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);
}
}

View File

@ -0,0 +1,27 @@
part of 'database.dart';
class SqliteException implements Exception {
final String message;
final String explanation;
SqliteException(this.message, [this.explanation]);
factory SqliteException._fromErrorCode(DatabasePointer db, [int code]) {
final dbMessage = CString.fromC(bindings.sqlite3_errmsg(db).cast());
String explanation;
if (code != null) {
explanation = CString.fromC(bindings.sqlite3_errstr(code).cast());
}
return SqliteException(dbMessage, explanation);
}
@override
String toString() {
if (explanation == null) {
return 'SqliteException: $message';
} else {
return 'SqliteException: $message, $explanation';
}
}
}

View File

@ -0,0 +1,126 @@
part of 'database.dart';
class PreparedStatement {
final StatementPointer _stmt;
final Database _db;
bool _closed = false;
bool _bound = false;
final List<Pointer> _allocatedWhileBinding = [];
PreparedStatement._(this._stmt, this._db);
void close() {
if (!_closed) {
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();
_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] = CString.fromC(bindings.sqlite3_column_name(_stmt, i).cast());
}
while (_step() == Errors.SQLITE_ROW) {
rows.add([for (var i = 0; i < columnCount; i++) _readValue(i)]);
}
_reset();
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 CString.fromC(bindings.sqlite3_column_text(_stmt, index).cast());
case Types.SQLITE_BLOB:
final length = bindings.sqlite3_column_bytes(_stmt, index);
final data =
CBlob.fromC(bindings.sqlite3_column_blob(_stmt, index), length);
return data;
case Types.SQLITE_NULL:
default:
return null;
}
}
/// Executes this prepared statement.
void execute([List<dynamic> params]) {
_ensureNotFinalized();
_bindParams(params);
final result = _step();
_reset();
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 = CString.allocate(param);
_allocatedWhileBinding.add(ptr);
bindings.sqlite3_bind_text(_stmt, i, ptr);
} else if (param is Uint8List) {
final ptr = CBlob.allocate(param);
_allocatedWhileBinding.add(ptr);
bindings.sqlite3_bind_blob(_stmt, i, ptr, param.length);
}
}
}
_bound = true;
}
int _step() => bindings.sqlite3_step(_stmt);
}

View File

@ -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;
}
}

View File

@ -0,0 +1,187 @@
// 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(CString filename, Pointer<DatabasePointer> databaseOut,
int flags, CString vfs) sqlite3_open_v2;
int Function(DatabasePointer database) sqlite3_close_v2;
void Function(Pointer<Void> ptr) sqlite3_free;
int Function(
DatabasePointer database,
CString query,
int nbytes,
Pointer<StatementPointer> statementOut,
Pointer<CString> tail) sqlite3_prepare_v2;
int Function(
DatabasePointer database,
CString query,
Pointer callback,
Pointer cbFirstArg,
Pointer errorMsgOut,
) sqlite3_exec;
int Function(StatementPointer statement) sqlite3_step;
int Function(StatementPointer statement) sqlite3_reset;
int Function(StatementPointer statement) sqlite3_finalize;
int Function(StatementPointer statement) sqlite3_column_count;
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_name;
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_decltype;
int Function(StatementPointer statement, int columnIndex) sqlite3_column_type;
ValuePointer Function(StatementPointer statement, int columnIndex)
sqlite3_column_value;
double Function(StatementPointer statement, int columnIndex)
sqlite3_column_double;
int Function(StatementPointer statement, int columnIndex) sqlite3_column_int;
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_text;
CBlob Function(StatementPointer statement, int columnIndex)
sqlite3_column_blob;
/// Returns the amount of bytes to read when using [sqlite3_column_blob].
int Function(StatementPointer statement, int columnIndex)
sqlite3_column_bytes;
int Function(DatabasePointer db) sqlite3_changes;
int Function(DatabasePointer db) sqlite3_last_insert_rowid;
CString Function(int code) sqlite3_errstr;
CString Function(DatabasePointer database) sqlite3_errmsg;
int Function(StatementPointer statement, int columnIndex, double value)
sqlite3_bind_double;
int Function(StatementPointer statement, int columnIndex, int value)
sqlite3_bind_int;
int Function(StatementPointer statement, int columnIndex, CString value)
sqlite3_bind_text;
int Function(
StatementPointer statement, int columnIndex, CBlob value, int length)
sqlite3_bind_blob;
int Function(StatementPointer 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();

View File

@ -2,6 +2,8 @@
// 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
@ -110,7 +112,7 @@ class Errors {
///
/// 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.
/// in the 4th parameter to the `sqlite3_vfs.xOpen` method.
class Flags {
/// Ok for sqlite3_open_v2()
static const int SQLITE_OPEN_READONLY = 0x00000001;

View File

@ -2,18 +2,19 @@
// 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 'dart:ffi';
import "../ffi/cstring.dart";
import "../ffi/blob.dart";
import '../ffi/blob.dart';
import "types.dart";
import 'types.dart';
typedef sqlite3_open_v2_native_t = Int32 Function(
CString filename, Pointer<DatabasePointer> ppDb, Int32 flags, CString vfs);
typedef sqlite3_close_v2_native_t = Int32 Function(DatabasePointer database);
typedef sqlite3_free_native = Function(Pointer<Void> pointer);
typedef sqlite3_prepare_v2_native_t = Int32 Function(
DatabasePointer database,
CString query,
@ -21,6 +22,13 @@ typedef sqlite3_prepare_v2_native_t = Int32 Function(
Pointer<StatementPointer> statementOut,
Pointer<CString> tail);
typedef sqlite3_exec_native = Int32 Function(
DatabasePointer database,
CString query,
Pointer callback,
Pointer firstCbArg,
Pointer<CString> errorOut);
typedef sqlite3_step_native_t = Int32 Function(StatementPointer statement);
typedef sqlite3_reset_native_t = Int32 Function(StatementPointer statement);
@ -55,6 +63,12 @@ typedef sqlite3_column_int_native_t = Int32 Function(
typedef sqlite3_column_text_native_t = CString Function(
StatementPointer statement, Int32 columnIndex);
typedef sqlite3_column_blob_native_t = CBlob Function(
StatementPointer statement, Int32 columnIndex);
typedef sqlite3_column_bytes_native_t = Int32 Function(
StatementPointer statement, Int32 columnIndex);
typedef sqlite3_changes_native = Int32 Function(DatabasePointer database);
typedef sqlite3_last_insert_rowid_native = Int64 Function(
DatabasePointer database);
@ -67,3 +81,5 @@ typedef sqlite3_bind_text_native = Int32 Function(
StatementPointer statement, Int32 columnIndex, CString value);
typedef sqlite3_bind_blob_native = Int32 Function(
StatementPointer statement, Int32 columnIndex, CBlob value, Int32 length);
typedef sqlite3_bind_null_native = Int32 Function(
StatementPointer statement, Int32 columnIndex);

View File

@ -2,7 +2,9 @@
// 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 'dart:ffi';
// ignore_for_file: comment_references
class FunctionPointer extends Pointer<Void> {}

View File

@ -0,0 +1,51 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
/// Pointer to arbitrary blobs that aren't null-terminated.
class CBlob extends Pointer<Uint8> {
/// Allocate a [CBlob] not managed in and populates it with [dartBlob].
factory CBlob.allocate(Uint8List dartBlob) {
final ptr = allocate(count: dartBlob.length);
for (var i = 0; i < dartBlob.length; ++i) {
ptr.elementAt(i).store(dartBlob[i]);
}
return ptr.cast();
}
/// Read the string from C memory into Dart.
static Uint8List fromC(CBlob str, int length) {
if (str == null) return null;
assert(length >= 0);
final units = Uint8List(length);
for (var i = 0; i < length; ++i) {
units[i] = str.elementAt(i).load();
}
return units;
}
}
/// A null-terminated C string.
class CString extends Pointer<Uint8> {
/// Allocate a [CString] not managed in and populates it with [string].
factory CString.allocate(String string) {
final encoded = utf8.encode(string);
final data = Uint8List(encoded.length + 1) // already filled with zeroes
..setAll(0, encoded);
return CBlob.allocate(data).cast();
}
/// Read the string from C memory into Dart.
static String fromC(CBlob str) {
if (str == null) return null;
var len = 0;
while (str.elementAt(++len).load<int>() != 0) {}
final list = CBlob.fromC(str, len);
return utf8.decode(list);
}
}

View File

@ -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);
}

View File

@ -16,10 +16,19 @@ abstract class _DatabaseUser extends QueryExecutor {
@override
Future<bool> ensureOpen() {
_db ??= Database(dbFile.absolute.path);
_db ??= _openInternal();
return Future.value(true);
}
Database _openInternal() {
if (dbFile == null) {
return Database.memory();
} else {
return Database.openFile(dbFile);
}
}
@override
Future<void> runCustom(String statement) {
_logStmt(statement, const []);
@ -27,40 +36,53 @@ abstract class _DatabaseUser extends QueryExecutor {
return Future.value();
}
Future<int> _executeWithArgs(String statement, List<dynamic> args) {
void _runWithArgs(String statement, List<dynamic> args) {
_logStmt(statement, args);
_db.execute(statement, params: args);
return Future.value(_db.changes());
if (args.isEmpty) {
_db.execute(statement);
} else {
_db.prepare(statement)
..execute(args)
..close();
}
}
Future<int> _runAndReturnAffected(String statement, List<dynamic> args) {
_runWithArgs(statement, args);
return Future.value(_db.updatedRows);
}
@override
Future<int> runDelete(String statement, List<dynamic> args) {
return _executeWithArgs(statement, args);
return _runAndReturnAffected(statement, args);
}
@override
Future<int> runUpdate(String statement, List<dynamic> args) {
return _executeWithArgs(statement, args);
return _runAndReturnAffected(statement, args);
}
@override
Future<int> runInsert(String statement, List<dynamic> args) {
_logStmt(statement, args);
_db.execute(statement, params: args);
return Future.value(_db.lastInsertId());
_runWithArgs(statement, args);
return Future.value(_db.lastInsertId);
}
@override
Future<List<Map<String, dynamic>>> runSelect(
String statement, List<dynamic> args) {
if (args.isNotEmpty) {
throw UnsupportedError(
'Select statements with variables are not yet supported.');
}
_logStmt(statement, args);
_db.query(statement);
// todo parse rows
return Future.value([]);
final stmt = _db.prepare(statement);
final result = stmt.select(args);
stmt.close();
return Future.value(result.toList());
}
@override
Future<void> close() {
_db?.close();
return Future.value();
}
}

View File

@ -15,9 +15,6 @@ dependencies:
meta: '>= 1.0.0 <2.0.0'
collection: '>= 1.0.0 <2.0.0'
synchronized: ^2.1.0
# experimental ffi bindings for sqlite
sqlite3_ffi:
path: ../sqlite
dev_dependencies:
moor_generator: ^1.6.0

7
sqlite/.gitignore vendored
View File

@ -1,7 +0,0 @@
.dart_tool
.gdb_history
.packages
.vscode
pubspec.lock
test.db
test.db-journal

View File

@ -1,47 +0,0 @@
This was taken from [putraxor/sqlite3_ffi](https://github.com/putraxor/sqlite3_ffi),
which in turn was taken from the sqlite example in the Dart sdk repo. Moor made the following
changes:
- Support binding `Uint8List`
- Bindings for `sqlite3_changes`
# SQLite3 wrapper for dart:ffi
This is an illustrative sample for how to use `dart:ffi`.
## Building and Running this Sample
Building and running this sample is done through pub.
Running `pub get` and `pub run example/main` should produce the following output.
```sh
$ pub get
Resolving dependencies... (6.8s)
+ analyzer 0.35.4
...
+ yaml 2.1.15
Downloading analyzer 0.35.4...
Downloading kernel 0.3.14...
Downloading front_end 0.1.14...
Changed 47 dependencies!
Precompiling executables... (18.0s)
Precompiled test:test.
```
```
$ pub run example/main
1 Chocolade chip cookie Chocolade cookie foo
2 Ginger cookie null 42
3 Cinnamon roll null null
1 Chocolade chip cookie Chocolade cookie foo
2 Ginger cookie null 42
expected exception on accessing result data after close: The result has already been closed.
expected this query to fail: no such column: non_existing_column (Code 1: SQL logic error)
```
## Tutorial
A tutorial walking through the code is available in [docs/sqlite-tutorial.md](docs/sqlite-tutorial.md).
For information on how to use this package within a Flutter app, see [docs/android.md].
(Note: iOS is not yet supported).

View File

@ -1 +0,0 @@
# Using an empty analysis options so that this package gets analyzed with the less strict default rules

View File

@ -1,53 +0,0 @@
**This documentation is for demonstration/testing purposes only!**
# Using FFI with Flutter
## Android
Before using the FFI on Android, you need to procure an Android-compatible build of the native library you want to link against.
It's important that the shared object(s) be compatible with ABI version you wish to target (or else, that you have multiple builds for different ABIs).
See [https://developer.android.com/ndk/guides/abis] for more details on Android ABIs.
Within Flutter, the target ABI is controlled by the `--target-platform` parameter to the `flutter` command.
The workflow for packaging a native library will depend significantly on the library itself, but to illustrate the challenges at play, we'll demonstrate how to build the SQLite library from source to use with the FFI on an Android device.
### Building SQLite for Android
Every Android device ships with a copy of the SQLite library (`/system/lib/libsqlite.so`).
Unfortunately, this library cannot be loaded directly by apps (see [https://developer.android.com/about/versions/nougat/android-7.0-changes#ndk]).
It is accessible only through Java.
Instead, we can build SQLite directly with the NDK.
First, download the SQLite "amalgamation" source from [https://www.sqlite.org/download.html].
For the sake of brevity, we'll assume the file has been saved as `sqlite-amalgamation-XXXXXXX.zip`, the Android SDK (with NDK extension) is available in `~/Android`, and we're on a Linux workstation.
```sh
unzip sqlite-amalgamation-XXXXXXX.zip
cd sqlite-amalgamation-XXXXXXX
~/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang -c sqlite3.c -o sqlite3.o
~/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld -shared sqlite3.o -o libsqlite3.so
```
Note the use of the `aarch64` prefix to the compiler: this indicates that we're building a shared object for the `arm64-v8a` ABI.
This will be important later.
### Update Gradle script
Next we need to instruct Gradle to package this library with the app, so it will be available to load off the Android device at runtime.
Create a folder `native-libraries` in the root folder of the app, and update the `android/app/build.gradle` file:
```groovy
android {
// ...
sourceSets {
main {
jniLibs.srcDir '${project.projectDir.path}/../../native-libraries'
}
}
}
```
Within the `native-libraries` folder, the libraries are organized by ABI.
Therefore, we must copy the compiled `libsqlite3.so` into `native-libraries/arm64-v8a/libsqlite3.so`.
If multiple sub-directories are present, the libraries from the sub-directory corresponding to the target ABI will be available in the application's linking path, so the library can be loaded with `ffi.DynamicLibrary.open("libsqlite3.so")` in Dart.
Finally, pass `--target-platform=android-arm64` to the `flutter` command when running or building the app since `libsqlite3.so` was compiled for the `arm64-v8a` ABI.

View File

@ -1,130 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xl="http://www.w3.org/1999/xlink" viewBox="27.846457 27.846457 426.19686 227.77166" width="426.19686" height="227.77166">
<defs>
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
<font-face font-family="Helvetica Neue" font-size="10" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
</defs>
<metadata> Produced by OmniGraffle 7.9.4
<dc:date>2019-03-13 09:56:08 +0000</dc:date>
</metadata>
<g id="Canvas_1" fill="none" fill-opacity="1" stroke="none" stroke-dasharray="none" stroke-opacity="1">
<title>Canvas 1</title>
<rect fill="white" x="27.846457" y="27.846457" width="426.19686" height="227.77166"/>
<g id="Canvas_1: Layer 1">
<title>Layer 1</title>
<g id="Graphic_3">
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 110.00952)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.703685" y="15">Flutter </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="33.448">App</tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.031685" y="69.896">(Imports </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.775685" y="88.34399">package)</tspan>
</text>
</g>
<g id="Graphic_5">
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(373.50395 137.45752)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855685" y="15">Native </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927685" y="33.448">Library</tspan>
</text>
</g>
<g id="Graphic_6">
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(316.81103 146.68152)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x=".5064575" y="15">dart:ffi</tspan>
</text>
</g>
<g id="Graphic_7">
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(118.38583 119.20552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.9014575" y="10">Package </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="15.571457" y="22.28">API</tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="1.8614575" y="46.56">(Does not </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.0514575" y="58.839996">expose </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.7764575" y="71.119995">dart:ffi)</tspan>
</text>
</g>
<g id="Graphic_8">
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" fill="white"/>
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 231.7209)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="135.79352" y="15">Dart</tspan>
</text>
</g>
<g id="Graphic_9">
<rect x="340.1575" y="226.77166" width="113.38583" height="28.346457" fill="white"/>
<rect x="340.1575" y="226.77166" width="113.38583" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(345.1575 231.7209)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="23.428915" y="15">C / C++</tspan>
</text>
</g>
<g id="Graphic_10">
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 38.244917)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="15">App </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
</text>
</g>
<g id="Graphic_11">
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" fill="white"/>
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(118.38583 38.244917)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="63.1006" y="15">Package</tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="57.9166" y="33.448">Developer</tspan>
</text>
</g>
<g id="Graphic_12">
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" fill="white"/>
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(316.81103 29.020918)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.234457" y="15">Dart </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.490457" y="33.448">VM </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.2264575" y="51.895996">Team</tspan>
</text>
</g>
<g id="Graphic_13">
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(260.11812 149.76552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.8064575" y="10">Bindings</tspan>
</text>
</g>
<g id="Graphic_14">
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(373.50395 29.020918)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855686" y="15">Native </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927686" y="33.448">Library </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236862" y="51.895996">Developer</tspan>
</text>
</g>
<g id="Graphic_15">
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(175.07874 106.92552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="18.074685" y="10">Package </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="2.8746848" y="22.28">Implementation</tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="46.56">(Code which </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.259685" y="58.839996">converts C++ </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".1996848" y="71.119995">abstractions into </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="28.074685" y="83.39999">Dart </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.629685" y="95.67999">abstractions)</tspan>
</text>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="27.846457 27.846457 511.23623 227.77166" width="511.23623" height="227.77166">
<defs>
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
<font-face font-family="Helvetica Neue" font-size="10" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
<font-face-src>
<font-face-name name="HelveticaNeue"/>
</font-face-src>
</font-face>
</defs>
<metadata> Produced by OmniGraffle 7.9.4
<dc:date>2019-03-13 09:53:08 +0000</dc:date>
</metadata>
<g id="Canvas_1" stroke-opacity="1" stroke="none" stroke-dasharray="none" fill-opacity="1" fill="none">
<title>Canvas 1</title>
<rect fill="white" x="27.846457" y="27.846457" width="511.23623" height="227.77166"/>
<g id="Canvas_1: Layer 1">
<title>Layer 1</title>
<g id="Graphic_3">
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 110.00952)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.703685" y="15">Flutter </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="33.448">App</tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.031685" y="69.896">(Imports </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.775685" y="88.34399">package)</tspan>
</text>
</g>
<g id="Graphic_5">
<rect x="453.5433" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="453.5433" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(458.5433 137.45752)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855685" y="15">Native </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927685" y="33.448">Library</tspan>
</text>
</g>
<g id="Graphic_6">
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(316.81103 146.68152)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x=".5064575" y="15">dart:ffi</tspan>
</text>
</g>
<g id="Graphic_7">
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(118.38583 119.20552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.9014575" y="10">Package </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="15.571457" y="22.28">API</tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="1.8614575" y="46.56">(Does not </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.0514575" y="58.839996">expose </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.7764575" y="71.119995">dart:ffi)</tspan>
</text>
</g>
<g id="Graphic_8">
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" fill="white"/>
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 231.7209)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="135.79352" y="15">Dart</tspan>
</text>
</g>
<g id="Graphic_9">
<rect x="340.1575" y="226.77166" width="198.4252" height="28.346457" fill="white"/>
<rect x="340.1575" y="226.77166" width="198.4252" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(345.1575 231.7209)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="65.9486" y="15">C / C++</tspan>
</text>
</g>
<g id="Graphic_10">
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(33.346457 38.244917)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="15">App </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
</text>
</g>
<g id="Graphic_11">
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" fill="white"/>
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(118.38583 38.244917)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="63.1006" y="15">Package</tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="57.9166" y="33.448">Developer</tspan>
</text>
</g>
<g id="Graphic_12">
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" fill="white"/>
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(316.81103 29.020918)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.234457" y="15">Dart </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.490457" y="33.448">VM </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.2264575" y="51.895996">Team</tspan>
</text>
</g>
<g id="Graphic_13">
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(260.11812 149.76552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.8064575" y="10">Bindings</tspan>
</text>
</g>
<g id="Graphic_14">
<rect x="453.5433" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
<rect x="453.5433" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(458.5433 29.020918)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855686" y="15">Native </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927686" y="33.448">Library </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236862" y="51.895996">Developer</tspan>
</text>
</g>
<g id="Graphic_15">
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(175.07874 106.92552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="18.074685" y="10">Package </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="2.8746848" y="22.28">Implementation</tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="46.56">(Code which </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.259685" y="58.839996">converts C++ </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".1996848" y="71.119995">abstractions into </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="28.074685" y="83.39999">Dart </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.629685" y="95.67999">abstractions)</tspan>
</text>
</g>
<g id="Graphic_16">
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(373.50395 38.244917)" fill="black">
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="6.407685" y="15">Package </tspan>
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
</text>
</g>
<g id="Graphic_17">
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<text transform="translate(373.50395 119.20552)" fill="black">
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="14.554685" y="10">Glue code</tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="34.28">(Code which </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.719685" y="46.56">takes care of </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="5.194685" y="58.839996">things such as </tspan>
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".7796848" y="71.119995">C++ exceptions)</tspan>
</text>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,234 +0,0 @@
# dart:ffi SQLite mini tutorial
In this mini tutorial we learn how to bind SQLite, a native library, in Dart using Dart's new foreign function interface `dart:ffi`.
We build a package which provides a Dartlike SQLite API using objects and `Iterator`s.
Inside the package we write Dart code which directly invokes C functions and manipulates C memory.
## Binding C Functions to Dart
The first step is to load a Native Library:
```dart
import "dart:ffi";
DynamicLibrary sqlite = dlopenPlatformSpecific("sqlite3");
```
In a `DynamicLibrary` we can `lookup` functions.
Let's lookup the function `sqlite3_prepare_v2` in the SQLite library.
That function has the following signature in the library header file.
```c++
SQLITE_API int sqlite3_prepare_v2(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL statement, UTF-8 encoded */
int nByte, /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
const char **pzTail /* OUT: Pointer to unused portion of zSql */
);
```
In order to lookup a function, we need a _C signature_ and a _Dart signature_.
```dart
typedef sqlite3_prepare_v2_native_t = Int32 Function(
DatabasePointer database,
CString query,
Int32 nbytes,
Pointer<StatementPointer> statementOut,
Pointer<CString> tail);
typedef Sqlite3_prepare_v2_t = int Function(
DatabasePointer database,
CString query,
int nbytes,
Pointer<StatementPointer> statementOut,
Pointer<CString> tail);
```
With these two signatures we can `lookup` the C function and expose it as a Dart function with `asFunction`.
```dart
Sqlite3_prepare_v2_t sqlite3_prepare_v2 = sqlite
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>("sqlite3_prepare_v2")
.asFunction();
```
Browse the code: [platform specific dynamic library loading](../lib/src/ffi/dylib_utils.dart), [C signatures](../lib/src/bindings/signatures.dart), [Dart signatures and bindings](../lib/src/bindings/bindings.dart), and [dart:ffi dynamic library interface](../../../../sdk/lib/ffi/dynamic_library.dart).
## Managing C Memory
In order to call `sqlite3_prepare_v2` to prepare a SQLite statement before executing, we need to be able to pass C pointers to C functions.
Database and Statement pointers are opaque pointers in the SQLite C API.
We specify these as classes extending `Pointer<Void>`.
```dart
class DatabasePointer extends Pointer<Void> {}
class StatementPointer extends Pointer<Void> {}
```
Strings in C are pointers to character arrays.
```dart
class CString extends Pointer<Uint8> {}
```
Pointers to C integers, floats, an doubles can be read from and written through to `dart:ffi`.
However, before we can write to C memory from dart, we need to `allocate` some memory.
```dart
Pointer<Uint8> p = allocate(); // Infers type argument allocate<Uint8>(), and allocates 1 byte.
p.store(123); // Stores a Dart int into this C int8.
int v = p.load(); // Infers type argument p.load<int>(), and loads a value from C memory.
```
Note that you can only load a Dart `int` from a C `Uint8`.
Trying to load a Dart `double` will result in a runtime exception.
We've almost modeled C Strings.
The last thing we need is to use this `Pointer` as an array.
We can do this by using `elementAt`.
```dart
CString string = allocate(count: 4).cast(); // Allocates 4 bytes and casts it to a string.
string.store(73); // Stores 'F' at index 0.
string.elementAt(1).store(73); // Stores 'F' at index 1.
string.elementAt(2).store(70); // Stores 'I' at index 2.
string.elementAt(3).store(0); // Null terminates the string.
```
We wrap the above logic of allocating strings in the constructor `CString.allocate`.
Now we have all ingredients to call `sqlite3_prepare_v2`.
```dart
Pointer<StatementPointer> statementOut = allocate();
CString queryC = CString.allocate(query);
int resultCode = sqlite3_prepare_v2(
_database, queryC, -1, statementOut, fromAddress(0));
```
With `dart:ffi` we are responsible for freeing C memory that we allocate.
So after calling `sqlite3_prepare_v2` we read out the statement pointer, and free the statement pointer pointer and `CString` which held the query string.
```
StatementPointer statement = statementOut.load();
statementOut.free();
queryC.free();
```
Browse the code: [CString class](../lib/src/ffi/cstring.dart), [code calling sqlite3_prepare_v2](../lib/src/database.dart#57), and [dart:ffi pointer interface](../../../../sdk/lib/ffi/ffi.dart).
## Dart API
We would like to present the users of our package with an object oriented API - not exposing any `dart:ffi` objects to them.
The SQLite C API returns a cursor to the first row of a result after executing a query.
We can read out the columns of this row and move the cursor to the next row.
The most natural way to expose this in Dart is through an `Iterable`.
We provide our package users with the following API.
```dart
class Result implements Iterable<Row> {}
class Row {
dynamic readColumnByIndex(int columnIndex) {}
dynamic readColumn(String columnName) {}
}
```
However, this interface does not completely match the semantics of the C API.
When we start reading the next `Row`, we do no longer have access to the previous `Row`.
We can model this by letting a `Row` keep track if its current or not.
```dart
class Row {
bool _isCurrentRow = true;
dynamic readColumnByIndex(int columnIndex) {
if (!_isCurrentRow) {
throw Exception(
"This row is not the current row, reading data from the non-current"
" row is not supported by sqlite.");
}
// ...
}
}
```
A second mismatch between Dart and C is that in C we have to manually release resources.
After executing a query and reading its results we need to call `sqlite3_finalize(statement)`.
We can take two approaches here, either we structure the API in such a way that users of our package (implicitly) release resources, or we use finalizers to release resources.
In this tutorial we take the first approach.
If our users iterate over all `Row`s, we can implicitly finalize the statement after they are done with the last row.
However, if they decide they do not want to iterate over the whole result, they need to explicitly state this.
In this tutorial, we use the `ClosableIterator` abstraction for `Iterators` with backing resources that need to be `close`d.
```dart
Result result = d.query("""
select id, name
from Cookies
;""");
for (Row r in result) {
String name = r.readColumn("name");
print(name);
}
// Implicitly closes the iterator.
result = d.query("""
select id, name
from Cookies
;""");
for (Row r in result) {
int id = r.readColumn("id");
if (id == 1) {
result.close(); // Explicitly closes the iterator, releasing underlying resources.
break;
}
}
```
Browse the code: [Database, Result, Row](../lib/src/database.dart), and [CloseableIterator](../lib/src/collections/closable_iterator.dart).
## Architecture Overview
The following diagram summarized what we have implemented as _package developers_ in this tutorial.
![architecture](lib/scenario-default.svg)
As the package developers wrapping an existing native library, we have only written Dart code - not any C/C++ code.
We specified bindings to the native library.
We have provided our package users with an object oriented API without exposing any `dart:ffi` objects.
And finally, we have implemented the package API by calling the C API.
## Current dart:ffi Development Status
In this minitutorial we used these `dart:ffi` features:
* Loading dynamic libararies and looking up C functions in these dynamic libraries.
* Calling C functions, with `dart:ffi` automatically marshalling arguments and return value.
* Manipulating C memory through `Pointer`s with `allocate`, `free`, `load`, `store`, and `elementAt`.
Features which we did not use in this tutorial:
* `@struct` on subtypes of `Pointer` to define a struct with fields. (However, this feature is likely to change in the future.)
Features which `dart:ffi` does not support yet:
* Callbacks from C back into Dart.
* Finalizers
* C++ Exceptions (Not on roadmap yet.)
Platform limitations:
* `dart:ffi` is only enabled on 64 bit Windows, Linux, and MacOS. (Arm64 and 32 bit Intel are under review.)
* `dart:ffi` only works in JIT mode, not in AOT.
It is possible to work around some of the current limitations by adding a C/C++ layer.
For example we could catch C++ exceptions in a C++ layer, and rethrow them in Dart.
The architecture diagram would change to the following in that case.
![architecture2](lib/scenario-full.svg)

View File

@ -1,89 +0,0 @@
import "../lib/sqlite.dart";
// ignore_for_file: dead_code
void main() {
Database d = Database("test.db");
d.execute("update Cookies set name='Changed' where name=?",
params: ['Google']);
var rows = d.query('select * from Cookies');
rows.forEach((row) {
var id = row.readColumnByIndexAsInt(0);
var name = row.readColumnByIndexAsText(1);
print('$id $name ');
});
return;
d.execute("drop table if exists Cookies;");
d.execute("""
create table Cookies (
id integer primary key,
name text not null,
alternative_name text
);""");
d.execute("""
insert into Cookies (id, name, alternative_name)
values
(1,'Chocolade chip cookie', 'Chocolade cookie'),
(2,'Ginger cookie', null),
(3,'Cinnamon roll', null)
;""");
Result result = d.query("""
select
id,
name,
alternative_name,
case
when id=1 then 'foo'
when id=2 then 42
when id=3 then null
end as multi_typed_column
from Cookies
;""");
for (Row r in result) {
int id = r.readColumnAsInt("id");
String name = r.readColumnByIndex(1);
String alternativeName = r.readColumn("alternative_name");
dynamic multiTypedValue = r.readColumn("multi_typed_column");
print("$id $name $alternativeName $multiTypedValue");
}
result = d.query("""
select
id,
name,
alternative_name,
case
when id=1 then 'foo'
when id=2 then 42
when id=3 then null
end as multi_typed_column
from Cookies
;""");
for (Row r in result) {
int id = r.readColumnAsInt("id");
String name = r.readColumnByIndex(1);
String alternativeName = r.readColumn("alternative_name");
dynamic multiTypedValue = r.readColumn("multi_typed_column");
print("$id $name $alternativeName $multiTypedValue");
if (id == 2) {
result.close();
break;
}
}
try {
result.iterator.moveNext();
} on SQLiteException catch (e) {
print("expected exception on accessing result data after close: $e");
}
try {
d.query("""
select
id,
non_existing_column
from Cookies
;""");
} on SQLiteException catch (e) {
print("expected this query to fail: $e");
}
//d.execute("drop table Cookies;");
d.close();
}

View File

@ -1,10 +0,0 @@
// 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.
/// A synchronous SQLite wrapper.
///
/// Written using dart:ffi.
library sqlite;
export "src/database.dart";

View File

@ -1,431 +0,0 @@
// 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/cstring.dart";
import '../ffi/blob.dart';
import "../ffi/dylib_utils.dart";
import "signatures.dart";
import "types.dart";
class _SQLiteBindings {
DynamicLibrary sqlite;
/// Opening A New Database Connection
///
/// ^These routines open an SQLite database file as specified by the
/// filename argument. ^The filename argument is interpreted as UTF-8 for
/// sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte
/// order for sqlite3_open16(). ^(A database connection handle is usually
/// returned in *ppDb, even if an error occurs. The only exception is that
/// if SQLite is unable to allocate memory to hold the sqlite3 object,
/// a NULL will be written into *ppDb instead of a pointer to the sqlite3
/// object.)^ ^(If the database is opened (and/or created) successfully, then
/// [SQLITE_OK] is returned. Otherwise an error code is returned.)^ ^The
/// [sqlite3_errmsg] or sqlite3_errmsg16() routines can be used to obtain
/// an English language description of the error following a failure of any
/// of the sqlite3_open() routines.
int Function(CString filename, Pointer<DatabasePointer> databaseOut,
int flags, CString vfs) sqlite3_open_v2;
int Function(DatabasePointer database) sqlite3_close_v2;
/// Compiling An SQL Statement
///
/// To execute an SQL query, it must first be compiled into a byte-code
/// program using one of these routines.
///
/// The first argument, "db", is a database connection obtained from a
/// prior successful call to sqlite3_open, [sqlite3_open_v2] or
/// sqlite3_open16. The database connection must not have been closed.
///
/// The second argument, "zSql", is the statement to be compiled, encoded
/// as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
/// interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2()
/// use UTF-16.
///
/// ^If the nByte argument is less than zero, then zSql is read up to the
/// first zero terminator. ^If nByte is non-negative, then it is the maximum
/// number of bytes read from zSql. ^When nByte is non-negative, the
/// zSql string ends at either the first '\000' or '\u0000' character or
/// the nByte-th byte, whichever comes first. If the caller knows
/// that the supplied string is nul-terminated, then there is a small
/// performance advantage to be gained by passing an nByte parameter that
/// is equal to the number of bytes in the input string <i>including</i>
/// the nul-terminator bytes.
///
/// ^If pzTail is not NULL then *pzTail is made to point to the first byte
/// past the end of the first SQL statement in zSql. These routines only
/// compile the first statement in zSql, so *pzTail is left pointing to
/// what remains uncompiled.
///
/// ^*ppStmt is left pointing to a compiled prepared statement that can be
/// executed using sqlite3_step. ^If there is an error, *ppStmt is set
/// to NULL. ^If the input text contains no SQL (if the input is an empty
/// string or a comment) then *ppStmt is set to NULL.
/// The calling procedure is responsible for deleting the compiled
/// SQL statement using [sqlite3_finalize] after it has finished with it.
/// ppStmt may not be NULL.
///
/// ^On success, the sqlite3_prepare family of routines return [SQLITE_OK];
/// otherwise an error code is returned.
///
/// The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
/// recommended for all new programs. The two older interfaces are retained
/// for backwards compatibility, but their use is discouraged.
/// ^In the "v2" interfaces, the prepared statement
/// that is returned (the sqlite3_stmt object) contains a copy of the
/// original SQL text. This causes the [sqlite3_step] interface to
/// behave differently in three ways:
int Function(
DatabasePointer database,
CString query,
int nbytes,
Pointer<StatementPointer> statementOut,
Pointer<CString> tail) sqlite3_prepare_v2;
/// Evaluate An SQL Statement
///
/// After a prepared statement has been prepared using either
/// [sqlite3_prepare_v2] or sqlite3_prepare16_v2() or one of the legacy
/// interfaces sqlite3_prepare() or sqlite3_prepare16(), this function
/// must be called one or more times to evaluate the statement.
///
/// The details of the behavior of the sqlite3_step() interface depend
/// on whether the statement was prepared using the newer "v2" interface
/// [sqlite3_prepare_v2] and sqlite3_prepare16_v2() or the older legacy
/// interface sqlite3_prepare() and sqlite3_prepare16(). The use of the
/// new "v2" interface is recommended for new applications but the legacy
/// interface will continue to be supported.
///
/// ^In the legacy interface, the return value will be either [SQLITE_BUSY],
/// [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE].
/// ^With the "v2" interface, any of the other [result codes] or
/// [extended result codes] might be returned as well.
///
/// ^[SQLITE_BUSY] means that the database engine was unable to acquire the
/// database locks it needs to do its job. ^If the statement is a [COMMIT]
/// or occurs outside of an explicit transaction, then you can retry the
/// statement. If the statement is not a [COMMIT] and occurs within an
/// explicit transaction then you should rollback the transaction before
/// continuing.
///
/// ^[SQLITE_DONE] means that the statement has finished executing
/// successfully. sqlite3_step() should not be called again on this virtual
/// machine without first calling [sqlite3_reset()] to reset the virtual
/// machine back to its initial state.
///
/// ^If the SQL statement being executed returns any data, then [SQLITE_ROW]
/// is returned each time a new row of data is ready for processing by the
/// caller. The values may be accessed using the [column access functions].
/// sqlite3_step() is called again to retrieve the next row of data.
///
/// ^[SQLITE_ERROR] means that a run-time error (such as a constraint
/// violation) has occurred. sqlite3_step() should not be called again on
/// the VM. More information may be found by calling [sqlite3_errmsg()].
/// ^With the legacy interface, a more specific error code (for example,
/// [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
/// can be obtained by calling [sqlite3_reset()] on the
/// prepared statement. ^In the "v2" interface,
/// the more specific error code is returned directly by sqlite3_step().
///
/// [SQLITE_MISUSE] means that the this routine was called inappropriately.
/// Perhaps it was called on a prepared statement that has
/// already been [sqlite3_finalize | finalized] or on one that had
/// previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
/// be the case that the same database connection is being used by two or
/// more threads at the same moment in time.
///
/// For all versions of SQLite up to and including 3.6.23.1, a call to
/// [sqlite3_reset] was required after sqlite3_step() returned anything
/// other than [Errors.SQLITE_ROW] before any subsequent invocation of
/// sqlite3_step(). Failure to reset the prepared statement using
/// [sqlite3_reset()] would result in an [Errors.SQLITE_MISUSE] return from
/// sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began
/// calling [sqlite3_reset] automatically in this circumstance rather
/// than returning [Errors.SQLITE_MISUSE]. This is not considered a
/// compatibility break because any application that ever receives an
/// [Errors.SQLITE_MISUSE] error is broken by definition. The
/// [SQLITE_OMIT_AUTORESET] compile-time option
/// can be used to restore the legacy behavior.
///
/// <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step()
/// API always returns a generic error code, [SQLITE_ERROR], following any
/// error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call
/// [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the
/// specific [error codes] that better describes the error.
/// We admit that this is a goofy design. The problem has been fixed
/// with the "v2" interface. If you prepare all of your SQL statements
/// using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
/// of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces,
/// then the more specific [error codes] are returned directly
/// by sqlite3_step(). The use of the "v2" interface is recommended.
int Function(StatementPointer statement) sqlite3_step;
/// CAPI3REF: Reset A Prepared Statement Object
///
/// The sqlite3_reset() function is called to reset a prepared statement
/// object back to its initial state, ready to be re-executed.
/// ^Any SQL statement variables that had values bound to them using
/// the sqlite3_bind_blob | sqlite3_bind_*() API retain their values.
/// Use sqlite3_clear_bindings() to reset the bindings.
///
/// ^The [sqlite3_reset] interface resets the prepared statement S
/// back to the beginning of its program.
///
/// ^If the most recent call to [sqlite3_step] for the
/// prepared statement S returned [Errors.SQLITE_ROW] or [Errors.SQLITE_DONE],
/// or if [sqlite3_step] has never before been called on S,
/// then [sqlite3_reset] returns [Errors.SQLITE_OK].
///
/// ^If the most recent call to [sqlite3_step(S)] for the
/// prepared statement S indicated an error, then
/// [sqlite3_reset] returns an appropriate [Errors].
///
/// ^The [sqlite3_reset] interface does not change the values
int Function(StatementPointer statement) sqlite3_reset;
/// Returns the number of rows modified, inserted or deleted by the most
/// recently completed INSERT, UPDATE or DELETE statement on the database
/// connection [db].
int Function(DatabasePointer db) sqlite3_changes;
int Function(DatabasePointer db) sqlite3_last_insert_rowid;
/// Destroy A Prepared Statement Object
///
/// ^The sqlite3_finalize() function is called to delete a prepared statement.
/// ^If the most recent evaluation of the statement encountered no errors
/// or if the statement is never been evaluated, then sqlite3_finalize()
/// returns SQLITE_OK. ^If the most recent evaluation of statement S failed,
/// then sqlite3_finalize(S) returns the appropriate error code or extended
/// error code.
///
/// ^The sqlite3_finalize(S) routine can be called at any point during
/// the life cycle of prepared statement S:
/// before statement S is ever evaluated, after
/// one or more calls to [sqlite3_reset], or after any call
/// to [sqlite3_step] regardless of whether or not the statement has
/// completed execution.
///
/// ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
///
/// The application must finalize every prepared statement in order to avoid
/// resource leaks. It is a grievous error for the application to try to use
/// a prepared statement after it has been finalized. Any use of a prepared
/// statement after it has been finalized can result in undefined and
/// undesirable behavior such as segfaults and heap corruption.
int Function(StatementPointer statement) sqlite3_finalize;
/// Number Of Columns In A Result Set
///
/// ^Return the number of columns in the result set returned by the
/// prepared statement. ^This routine returns 0 if pStmt is an SQL
/// statement that does not return data (for example an [UPDATE]).
int Function(StatementPointer statement) sqlite3_column_count;
/// Column Names In A Result Set
///
/// ^These routines return the name assigned to a particular column
/// in the result set of a SELECT statement. ^The sqlite3_column_name()
/// interface returns a pointer to a zero-terminated UTF-8 string
/// and sqlite3_column_name16() returns a pointer to a zero-terminated
/// UTF-16 string. ^The first parameter is the prepared statement
/// that implements the SELECT statement. ^The second parameter is the
/// column number. ^The leftmost column is number 0.
///
/// ^The returned string pointer is valid until either the prepared statement
/// is destroyed by [sqlite3_finalize] or until the statement is automatically
/// reprepared by the first call to [sqlite3_step] for a particular run
/// or until the next call to
/// sqlite3_column_name() or sqlite3_column_name16() on the same column.
///
/// ^If sqlite3_malloc() fails during the processing of either routine
/// (for example during a conversion from UTF-8 to UTF-16) then a
/// NULL pointer is returned.
///
/// ^The name of a result column is the value of the "AS" clause for
/// that column, if there is an AS clause. If there is no AS clause
/// then the name of the column is unspecified and may change from
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_name;
/// CAPI3REF: Declared Datatype Of A Query Result
///
/// ^(The first parameter is a prepared statement.
/// If this statement is a SELECT statement and the Nth column of the
/// returned result set of that SELECT is a table column (not an
/// expression or subquery) then the declared type of the table
/// column is returned.)^ ^If the Nth column of the result set is an
/// expression or subquery, then a NULL pointer is returned.
/// ^The returned string is always UTF-8 encoded.
///
/// ^(For example, given the database schema:
///
/// CREATE TABLE t1(c1 VARIANT);
///
/// and the following statement to be compiled:
///
/// SELECT c1 + 1, c1 FROM t1;
///
/// this routine would return the string "VARIANT" for the second result
/// column (i==1), and a NULL pointer for the first result column (i==0).)^
///
/// ^SQLite uses dynamic run-time typing. ^So just because a column
/// is declared to contain a particular type does not mean that the
/// data stored in that column is of the declared type. SQLite is
/// strongly typed, but the typing is dynamic not static. ^Type
/// is associated with individual values, not with the containers
/// used to hold those values.
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_decltype;
int Function(StatementPointer statement, int columnIndex) sqlite3_column_type;
ValuePointer Function(StatementPointer statement, int columnIndex)
sqlite3_column_value;
double Function(StatementPointer statement, int columnIndex)
sqlite3_column_double;
int Function(StatementPointer statement, int columnIndex) sqlite3_column_int;
CString Function(StatementPointer statement, int columnIndex)
sqlite3_column_text;
/// The sqlite3_errstr() interface returns the English-language text that
/// describes the result code, as UTF-8. Memory to hold the error message
/// string is managed internally and must not be freed by the application.
CString Function(int code) sqlite3_errstr;
/// Error Codes And Messages
///
/// ^The sqlite3_errcode() interface returns the numeric [result code] or
/// [extended result code] for the most recent failed sqlite3_* API call
/// associated with a [database connection]. If a prior API call failed
/// but the most recent API call succeeded, the return value from
/// sqlite3_errcode() is undefined. ^The sqlite3_extended_errcode()
/// interface is the same except that it always returns the
/// [extended result code] even when extended result codes are
/// disabled.
///
/// ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
/// text that describes the error, as either UTF-8 or UTF-16 respectively.
/// ^(Memory to hold the error message string is managed internally.
/// The application does not need to worry about freeing the result.
/// However, the error string might be overwritten or deallocated by
/// subsequent calls to other SQLite interface functions.)^
///
/// When the serialized [threading mode] is in use, it might be the
/// case that a second error occurs on a separate thread in between
/// the time of the first error and the call to these interfaces.
/// When that happens, the second error will be reported since these
/// interfaces always report the most recent result. To avoid
/// this, each thread can obtain exclusive use of the [database connection] D
/// by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning
/// to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after
/// all calls to the interfaces listed here are completed.
///
/// If an interface fails with SQLITE_MISUSE, that means the interface
/// was invoked incorrectly by the application. In that case, the
/// error code and message may or may not be set.
CString Function(DatabasePointer database) sqlite3_errmsg;
int Function(StatementPointer statement, int columnIndex, double value)
sqlite3_bind_double;
int Function(StatementPointer statement, int columnIndex, int value)
sqlite3_bind_int;
int Function(StatementPointer statement, int columnIndex, CString value)
sqlite3_bind_text;
int Function(
StatementPointer statement, int columnIndex, CBlob value, int length)
sqlite3_bind_blob;
_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_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_prepare_v2 = sqlite
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
"sqlite3_prepare_v2")
.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();
}
}
_SQLiteBindings _cachedBindings;
_SQLiteBindings get bindings => _cachedBindings ??= _SQLiteBindings();

View File

@ -1,29 +0,0 @@
// 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.
/// This iterator should be [close]d after use.
///
/// [ClosableIterator]s often use resources which should be freed after use.
/// The consumer of the iterator can either manually [close] the iterator, or
/// consume all elements on which the iterator will automatically be closed.
abstract class ClosableIterator<T> extends Iterator<T> {
/// Close this iterator.
void close();
/// Moves to the next element and [close]s the iterator if it was the last
/// element.
bool moveNext();
}
/// This iterable's iterator should be [close]d after use.
///
/// Companion class of [ClosableIterator].
abstract class ClosableIterable<T> extends Iterable<T> {
/// Close this iterables iterator.
void close();
/// Returns a [ClosableIterator] that allows iterating the elements of this
/// [ClosableIterable].
ClosableIterator<T> get iterator;
}

View File

@ -1,343 +0,0 @@
// 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:collection";
import 'dart:convert';
import "dart:ffi";
import 'dart:typed_data';
import "bindings/bindings.dart";
import "bindings/types.dart";
import "bindings/constants.dart";
import "collections/closable_iterator.dart";
import "ffi/cstring.dart";
import 'ffi/blob.dart';
/// [Database] represents an open connection to a SQLite database.
///
/// All functions against a database may throw [SQLiteError].
///
/// This database interacts with SQLite synchonously.
class Database {
DatabasePointer _database;
bool _open = false;
/// Open a database located at the file [path].
Database(String path,
[int flags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE]) {
Pointer<DatabasePointer> dbOut = allocate();
CString pathC = CString.allocate(path);
final int resultCode =
bindings.sqlite3_open_v2(pathC, dbOut, flags, fromAddress(0));
_database = dbOut.load();
dbOut.free();
pathC.free();
if (resultCode == Errors.SQLITE_OK) {
_open = true;
} else {
// Even if "open" fails, sqlite3 will still create a database object. We
// can just destroy it.
SQLiteException exception = _loadError(resultCode);
close();
throw exception;
}
}
/// Close the database.
///
/// This should only be called once on a database unless an exception is
/// thrown. It should be called at least once to finalize the database and
/// avoid resource leaks.
void close() {
assert(_open);
final int resultCode = bindings.sqlite3_close_v2(_database);
if (resultCode == Errors.SQLITE_OK) {
_open = false;
} else {
throw _loadError(resultCode);
}
}
/// Execute a query, discarding any returned rows.
void execute(String query, {List params}) {
Pointer<StatementPointer> statementOut = allocate();
CString queryC = CString.allocate(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, fromAddress(0));
StatementPointer statement = statementOut.load();
if (params != null) {
for (var i = 0; i < params.length; i++) {
if (params[i].runtimeType.toString() == 'int') {
bindings.sqlite3_bind_int(statement, i + 1, params[i]);
}
if (params[i].runtimeType.toString() == 'double') {
bindings.sqlite3_bind_double(statement, i + 1, params[i]);
}
if (params[i].runtimeType.toString() == 'String') {
String param = utf8.decode(utf8.encode(params[i]));
var text = CString.allocate(param);
bindings.sqlite3_bind_text(statement, i + 1, text);
text.free();
}
if (params[i].runtimeType.toString() == 'Uint8List') {
Uint8List param = params[i];
final blob = CBlob.allocate(param);
bindings.sqlite3_bind_blob(statement, i + 1, blob, param.length);
}
}
}
statementOut.free();
queryC.free();
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
resultCode = bindings.sqlite3_step(statement);
}
bindings.sqlite3_finalize(statement);
if (resultCode != Errors.SQLITE_DONE) {
throw _loadError(resultCode);
}
}
/// Returns the number of rows modified by the most recently completed
/// INSERT, UPDATE or DELETE statement.
int changes() {
return bindings.sqlite3_changes(_database);
}
int lastInsertId() {
return bindings.sqlite3_last_insert_rowid(_database);
}
/// Evaluate a query and return the resulting rows as an iterable.
Result query(String query) {
Pointer<StatementPointer> statementOut = allocate();
CString queryC = CString.allocate(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, fromAddress(0));
StatementPointer statement = statementOut.load();
statementOut.free();
queryC.free();
if (resultCode != Errors.SQLITE_OK) {
bindings.sqlite3_finalize(statement);
throw _loadError(resultCode);
}
Map<String, int> columnIndices = {};
int columnCount = bindings.sqlite3_column_count(statement);
for (int i = 0; i < columnCount; i++) {
String columnName =
CString.fromUtf8(bindings.sqlite3_column_name(statement, i));
columnIndices[columnName] = i;
}
return Result._(this, statement, columnIndices);
}
SQLiteException _loadError([int errorCode]) {
String errorMessage = CString.fromUtf8(bindings.sqlite3_errmsg(_database));
if (errorCode == null) {
return SQLiteException(errorMessage);
}
String errorCodeExplanation =
CString.fromUtf8(bindings.sqlite3_errstr(errorCode));
return SQLiteException(
"$errorMessage (Code $errorCode: $errorCodeExplanation)");
}
}
/// [Result] represents a [Database.query]'s result and provides an [Iterable]
/// interface for the results to be consumed.
///
/// Please note that this iterator should be [close]d manually if not all [Row]s
/// are consumed.
class Result extends IterableBase<Row> implements ClosableIterable<Row> {
final Database _database;
final ClosableIterator<Row> _iterator;
final StatementPointer _statement;
final Map<String, int> _columnIndices;
Row _currentRow = null;
Result._(
this._database,
this._statement,
this._columnIndices,
) : _iterator = _ResultIterator(_statement, _columnIndices) {}
void close() => _iterator.close();
ClosableIterator<Row> get iterator => _iterator;
}
class _ResultIterator implements ClosableIterator<Row> {
final StatementPointer _statement;
final Map<String, int> _columnIndices;
Row _currentRow = null;
bool _closed = false;
_ResultIterator(this._statement, this._columnIndices) {}
bool moveNext() {
if (_closed) {
throw SQLiteException("The result has already been closed.");
}
_currentRow?._setNotCurrent();
int stepResult = bindings.sqlite3_step(_statement);
if (stepResult == Errors.SQLITE_ROW) {
_currentRow = Row._(_statement, _columnIndices);
return true;
} else {
close();
return false;
}
}
Row get current {
if (_closed) {
throw SQLiteException("The result has already been closed.");
}
return _currentRow;
}
void close() {
_currentRow?._setNotCurrent();
_closed = true;
bindings.sqlite3_finalize(_statement);
}
}
class Row {
final StatementPointer _statement;
final Map<String, int> _columnIndices;
bool _isCurrentRow = true;
Row._(this._statement, this._columnIndices) {}
/// Reads column [columnName].
///
/// By default it returns a dynamically typed value. If [convert] is set to
/// [Convert.StaticType] the value is converted to the static type computed
/// for the column by the query compiler.
dynamic readColumn(String columnName,
{Convert convert = Convert.DynamicType}) {
return readColumnByIndex(_columnIndices[columnName], convert: convert);
}
/// Reads column [columnName].
///
/// By default it returns a dynamically typed value. If [convert] is set to
/// [Convert.StaticType] the value is converted to the static type computed
/// for the column by the query compiler.
dynamic readColumnByIndex(int columnIndex,
{Convert convert = Convert.DynamicType}) {
_checkIsCurrentRow();
Type dynamicType;
if (convert == Convert.DynamicType) {
dynamicType =
_typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
} else {
dynamicType = _typeFromText(CString.fromUtf8(
bindings.sqlite3_column_decltype(_statement, columnIndex)));
}
switch (dynamicType) {
case Type.Integer:
return readColumnByIndexAsInt(columnIndex);
case Type.Text:
return readColumnByIndexAsText(columnIndex);
case Type.Null:
return null;
break;
default:
}
}
/// Reads column [columnName] and converts to [Type.Integer] if not an
/// integer.
int readColumnAsInt(String columnName) {
return readColumnByIndexAsInt(_columnIndices[columnName]);
}
/// Reads column [columnIndex] and converts to [Type.Integer] if not an
/// integer.
int readColumnByIndexAsInt(int columnIndex) {
_checkIsCurrentRow();
return bindings.sqlite3_column_int(_statement, columnIndex);
}
/// Reads column [columnName] and converts to [Type.Text] if not text.
String readColumnAsText(String columnName) {
return readColumnByIndexAsText(_columnIndices[columnName]);
}
/// Reads column [columnIndex] and converts to [Type.Text] if not text.
String readColumnByIndexAsText(int columnIndex) {
_checkIsCurrentRow();
return CString.fromUtf8(
bindings.sqlite3_column_text(_statement, columnIndex));
}
void _checkIsCurrentRow() {
if (!_isCurrentRow) {
throw Exception(
"This row is not the current row, reading data from the non-current"
" row is not supported by sqlite.");
}
}
void _setNotCurrent() {
_isCurrentRow = false;
}
}
Type _typeFromCode(int code) {
switch (code) {
case Types.SQLITE_INTEGER:
return Type.Integer;
case Types.SQLITE_FLOAT:
return Type.Float;
case Types.SQLITE_TEXT:
return Type.Text;
case Types.SQLITE_BLOB:
return Type.Blob;
case Types.SQLITE_NULL:
return Type.Null;
}
throw Exception("Unknown type [$code]");
}
Type _typeFromText(String textRepresentation) {
switch (textRepresentation) {
case "integer":
return Type.Integer;
case "float":
return Type.Float;
case "text":
return Type.Text;
case "blob":
return Type.Blob;
case "null":
return Type.Null;
}
if (textRepresentation == null) return Type.Null;
throw Exception("Unknown type [$textRepresentation]");
}
enum Type { Integer, Float, Text, Blob, Null }
enum Convert { DynamicType, StaticType }
class SQLiteException {
final String message;
SQLiteException(this.message);
String toString() => message;
}

View File

@ -1,57 +0,0 @@
// 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:async";
import "dart:ffi";
/// [Arena] manages allocated C memory.
///
/// Arenas are zoned.
class Arena {
Arena();
List<Pointer<Void>> _allocations = [];
/// Bound the lifetime of [ptr] to this [Arena].
T scoped<T extends Pointer>(T ptr) {
_allocations.add(ptr.cast());
return ptr;
}
/// Frees all memory pointed to by [Pointer]s in this arena.
void finalize() {
for (final ptr in _allocations) {
ptr.free();
}
}
/// The last [Arena] in the zone.
factory Arena.current() {
return Zone.current[#_currentArena];
}
}
/// Bound the lifetime of [ptr] to the current [Arena].
T scoped<T extends Pointer>(T ptr) => Arena.current().scoped(ptr);
class RethrownError {
dynamic original;
StackTrace originalStackTrace;
RethrownError(this.original, this.originalStackTrace);
toString() => """RethrownError(${original})
${originalStackTrace}""";
}
/// Runs the [body] in an [Arena] freeing all memory which is [scoped] during
/// execution of [body] at the end of the execution.
R runArena<R>(R Function(Arena) body) {
Arena arena = Arena();
try {
return runZoned(() => body(arena),
zoneValues: {#_currentArena: arena},
onError: (error, st) => throw RethrownError(error, st));
} finally {
arena.finalize();
}
}

View File

@ -1,40 +0,0 @@
import 'dart:ffi';
import 'dart:typed_data';
import 'arena.dart';
/// Represents a blob in C memory, managed by an [Arena]. The main difference
/// to a [CString] is that blobs aren't null-terminated.
class CBlob extends Pointer<Void> {
/// Allocates a [CBlob] in the current [Arena] and populates it with
/// [blob].
factory CBlob(Uint8List blob) => CBlob.inArena(Arena.current(), blob);
/// Allocates a [CString] in [arena] and populates it with [blob].
factory CBlob.inArena(Arena arena, Uint8List blob) =>
arena.scoped(CBlob.allocate(blob));
/// Allocate a [CBlob] not managed in and populates it with [dartBlob].
///
/// This [CBlob] is not managed by an [Arena]. Please ensure to [free] the
/// memory manually!
factory CBlob.allocate(Uint8List dartBlob) {
Pointer<Uint8> str = allocate(count: dartBlob.length);
for (int i = 0; i < dartBlob.length; ++i) {
str.elementAt(i).store(dartBlob[i]);
}
return str.cast();
}
/// Read the string for C memory into Dart.
static Uint8List fromC(CBlob str) {
if (str == null) return null;
int len = 0;
while (str.elementAt(++len).load<int>() != 0);
final Uint8List units = Uint8List(len);
for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load();
return units;
}
}

View File

@ -1,43 +0,0 @@
// 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:convert";
import "dart:ffi";
import "arena.dart";
/// Represents a String in C memory, managed by an [Arena].
class CString extends Pointer<Uint8> {
/// Allocates a [CString] in the current [Arena] and populates it with
/// [dartStr].
factory CString(String dartStr) => CString.inArena(Arena.current(), dartStr);
/// Allocates a [CString] in [arena] and populates it with [dartStr].
factory CString.inArena(Arena arena, String dartStr) =>
arena.scoped(CString.allocate(dartStr));
/// Allocate a [CString] not managed in and populates it with [dartStr].
///
/// This [CString] is not managed by an [Arena]. Please ensure to [free] the
/// memory manually!
factory CString.allocate(String dartStr) {
List<int> units = Utf8Encoder().convert(dartStr);
Pointer<Uint8> str = allocate(count: units.length + 1);
for (int i = 0; i < units.length; ++i) {
str.elementAt(i).store(units[i]);
}
str.elementAt(units.length).store(0);
return str.cast();
}
/// Read the string for C memory into Dart.
static String fromUtf8(CString str) {
if (str == null) return null;
int len = 0;
while (str.elementAt(++len).load<int>() != 0);
List<int> units = List(len);
for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load();
return Utf8Decoder().convert(units);
}
}

View File

@ -1,20 +0,0 @@
// 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' as ffi;
import 'dart:io' show Platform;
String _platformPath(String name, {String path}) {
if (path == null) path = "";
if (Platform.isLinux || Platform.isAndroid)
return path + "lib" + name + ".so";
if (Platform.isMacOS) return path + "lib" + name + ".dylib";
if (Platform.isWindows) return path + name + ".dll";
throw Exception("Platform not implemented");
}
ffi.DynamicLibrary dlopenPlatformSpecific(String name, {String path}) {
String fullPath = _platformPath(name, path: path);
return ffi.DynamicLibrary.open(fullPath);
}

View File

@ -1,9 +0,0 @@
name: sqlite3_ffi
version: 0.0.1
description: >-
Sqlite3 wrapper for dart:ffi.
author: Daco Harkes <dacoharkes@google.com>, Samir Jindel <sjindel@google.com>
environment:
sdk: '>=2.1.0 <3.0.0'
dev_dependencies:
test: ^1.5.3

View File

@ -1,164 +0,0 @@
// 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.
// VMOptions=--optimization-counter-threshold=5
import "package:test/test.dart";
import '../lib/sqlite.dart';
void main() {
test("sqlite integration test", () {
Database d = Database("test.db");
d.execute("drop table if exists Cookies;");
d.execute("""
create table Cookies (
id integer primary key,
name text not null,
alternative_name text
);""");
d.execute("""
insert into Cookies (id, name, alternative_name)
values
(1,'Chocolade chip cookie', 'Chocolade cookie'),
(2,'Ginger cookie', null),
(3,'Cinnamon roll', null)
;""");
Result result = d.query("""
select
id,
name,
alternative_name,
case
when id=1 then 'foo'
when id=2 then 42
when id=3 then null
end as multi_typed_column
from Cookies
;""");
for (Row r in result) {
int id = r.readColumnAsInt("id");
expect(true, 1 <= id && id <= 3);
String name = r.readColumnByIndex(1);
expect(true, name is String);
String alternativeName = r.readColumn("alternative_name");
expect(true, alternativeName is String || alternativeName == null);
dynamic multiTypedValue = r.readColumn("multi_typed_column");
expect(
true,
multiTypedValue == 42 ||
multiTypedValue == 'foo' ||
multiTypedValue == null);
print("$id $name $alternativeName $multiTypedValue");
}
result = d.query("""
select
id,
name,
alternative_name,
case
when id=1 then 'foo'
when id=2 then 42
when id=3 then null
end as multi_typed_column
from Cookies
;""");
for (Row r in result) {
int id = r.readColumnAsInt("id");
expect(true, 1 <= id && id <= 3);
String name = r.readColumnByIndex(1);
expect(true, name is String);
String alternativeName = r.readColumn("alternative_name");
expect(true, alternativeName is String || alternativeName == null);
dynamic multiTypedValue = r.readColumn("multi_typed_column");
expect(
true,
multiTypedValue == 42 ||
multiTypedValue == 'foo' ||
multiTypedValue == null);
print("$id $name $alternativeName $multiTypedValue");
if (id == 2) {
result.close();
break;
}
}
try {
result.iterator.moveNext();
} on SQLiteException catch (e) {
print("expected exception on accessing result data after close: $e");
}
try {
d.query("""
select
id,
non_existing_column
from Cookies
;""");
} on SQLiteException catch (e) {
print("expected this query to fail: $e");
}
d.execute("drop table Cookies;");
d.close();
});
test("concurrent db open and queries", () {
Database d = Database("test.db");
Database d2 = Database("test.db");
d.execute("drop table if exists Cookies;");
d.execute("""
create table Cookies (
id integer primary key,
name text not null,
alternative_name text
);""");
d.execute("""
insert into Cookies (id, name, alternative_name)
values
(1,'Chocolade chip cookie', 'Chocolade cookie'),
(2,'Ginger cookie', null),
(3,'Cinnamon roll', null)
;""");
Result r = d.query("select * from Cookies;");
Result r2 = d2.query("select * from Cookies;");
r.iterator..moveNext();
r2.iterator..moveNext();
r.iterator..moveNext();
Result r3 = d2.query("select * from Cookies;");
r3.iterator..moveNext();
expect(2, r.iterator.current.readColumn("id"));
expect(1, r2.iterator.current.readColumn("id"));
expect(1, r3.iterator.current.readColumn("id"));
r.close();
r2.close();
r3.close();
d.close();
d2.close();
});
test("stress test", () {
Database d = Database("test.db");
d.execute("drop table if exists Cookies;");
d.execute("""
create table Cookies (
id integer primary key,
name text not null,
alternative_name text
);""");
int repeats = 100;
for (int i = 0; i < repeats; i++) {
d.execute("""
insert into Cookies (name, alternative_name)
values
('Chocolade chip cookie', 'Chocolade cookie'),
('Ginger cookie', null),
('Cinnamon roll', null)
;""");
}
Result r = d.query("select count(*) from Cookies;");
int count = r.first.readColumnByIndexAsInt(0);
expect(count, 3 * repeats);
r.close();
d.close();
});
}