[samples/ffi] Update sqlite sample
The sample wasn't working for the longest time. Fixes: - Null safety, and late fields. - `dart pub` instead of `pub`. - Use the new `NativeFinalizer` and `Finalizable` features. Change-Id: I0a397abae511ab3f6762d1b2c4047226d15e36d6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/143804 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Michael Thomsen <mit@google.com>
This commit is contained in:
@@ -20,25 +20,10 @@ Invalid argument(s): Failed to load dynamic library (126)
|
||||
## 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.
|
||||
Running `dart 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
|
||||
$ dart run example/main
|
||||
1 Chocolade chip cookie Chocolade cookie foo
|
||||
2 Ginger cookie null 42
|
||||
3 Cinnamon roll null null
|
||||
|
||||
@@ -34,6 +34,10 @@ class _SQLiteBindings {
|
||||
Pointer<Utf8> vfs) sqlite3_open_v2;
|
||||
|
||||
late int Function(Pointer<Database> database) sqlite3_close_v2;
|
||||
late Pointer<NativeFunction<sqlite3_close_v2_native_t>>
|
||||
sqlite3_close_v2_native;
|
||||
late Pointer<NativeFunction<Void Function(Pointer<Database> database)>>
|
||||
sqlite3_close_v2_native_return_void;
|
||||
|
||||
/// Compiling An SQL Statement
|
||||
///
|
||||
@@ -214,6 +218,10 @@ class _SQLiteBindings {
|
||||
/// statement after it has been finalized can result in undefined and
|
||||
/// undesirable behavior such as segfaults and heap corruption.
|
||||
late int Function(Pointer<Statement> statement) sqlite3_finalize;
|
||||
late Pointer<NativeFunction<sqlite3_finalize_native_t>>
|
||||
sqlite3_finalize_native;
|
||||
late Pointer<NativeFunction<Void Function(Pointer<Statement> statement)>>
|
||||
sqlite3_finalize_native_return_void;
|
||||
|
||||
/// Number Of Columns In A Result Set
|
||||
///
|
||||
@@ -336,9 +344,10 @@ class _SQLiteBindings {
|
||||
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_close_v2_native = sqlite
|
||||
.lookup<NativeFunction<sqlite3_close_v2_native_t>>("sqlite3_close_v2");
|
||||
sqlite3_close_v2_native_return_void = sqlite3_close_v2_native.cast();
|
||||
sqlite3_close_v2 = sqlite3_close_v2_native.asFunction();
|
||||
sqlite3_prepare_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
|
||||
"sqlite3_prepare_v2")
|
||||
@@ -349,9 +358,10 @@ class _SQLiteBindings {
|
||||
sqlite3_reset = sqlite
|
||||
.lookup<NativeFunction<sqlite3_reset_native_t>>("sqlite3_reset")
|
||||
.asFunction();
|
||||
sqlite3_finalize = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>("sqlite3_finalize")
|
||||
.asFunction();
|
||||
sqlite3_finalize_native = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>("sqlite3_finalize");
|
||||
sqlite3_finalize_native_return_void = sqlite3_finalize_native.cast();
|
||||
sqlite3_finalize = sqlite3_finalize_native.asFunction();
|
||||
sqlite3_errstr = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errstr_native_t>>("sqlite3_errstr")
|
||||
.asFunction();
|
||||
|
||||
@@ -21,19 +21,19 @@ import "collections/closable_iterator.dart";
|
||||
///
|
||||
/// This database interacts with SQLite synchonously.
|
||||
class Database {
|
||||
late Pointer<types.Database> _database;
|
||||
late DatabaseResource _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<Pointer<types.Database>> dbOut = calloc();
|
||||
final pathC = path.toNativeUtf8();
|
||||
final pathC = Utf8Resource(path.toNativeUtf8());
|
||||
final int resultCode =
|
||||
bindings.sqlite3_open_v2(pathC, dbOut, flags, nullptr);
|
||||
_database = dbOut.value;
|
||||
bindings.sqlite3_open_v2(pathC.unsafe(), dbOut, flags, nullptr);
|
||||
_database = DatabaseResource(dbOut.value);
|
||||
calloc.free(dbOut);
|
||||
calloc.free(pathC);
|
||||
pathC.free();
|
||||
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = true;
|
||||
@@ -53,7 +53,7 @@ class Database {
|
||||
/// avoid resource leaks.
|
||||
void close() {
|
||||
assert(_open);
|
||||
final int resultCode = bindings.sqlite3_close_v2(_database);
|
||||
final int resultCode = _database.close();
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = false;
|
||||
} else {
|
||||
@@ -63,18 +63,17 @@ class Database {
|
||||
|
||||
/// Execute a query, discarding any returned rows.
|
||||
void execute(String query) {
|
||||
Pointer<Pointer<Statement>> statementOut = calloc();
|
||||
Pointer<Utf8> queryC = query.toNativeUtf8();
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, nullptr);
|
||||
Pointer<Statement> statement = statementOut.value;
|
||||
Pointer<Pointer<Statement>> statementOut = malloc();
|
||||
final queryC = Utf8Resource(query.toNativeUtf8());
|
||||
int resultCode = _database.prepare(queryC, -1, statementOut, nullptr);
|
||||
final statement = StatementResource(statementOut.value);
|
||||
calloc.free(statementOut);
|
||||
calloc.free(queryC);
|
||||
queryC.free();
|
||||
|
||||
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
|
||||
resultCode = bindings.sqlite3_step(statement);
|
||||
resultCode = statement.step();
|
||||
}
|
||||
bindings.sqlite3_finalize(statement);
|
||||
statement.finalize();
|
||||
if (resultCode != Errors.SQLITE_DONE) {
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
@@ -82,32 +81,33 @@ class Database {
|
||||
|
||||
/// Evaluate a query and return the resulting rows as an iterable.
|
||||
Result query(String query) {
|
||||
Pointer<Pointer<Statement>> statementOut = calloc();
|
||||
Pointer<Utf8> queryC = query.toNativeUtf8();
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, nullptr);
|
||||
Pointer<Statement> statement = statementOut.value;
|
||||
Pointer<Pointer<Statement>> statementOut = malloc();
|
||||
final queryC = Utf8Resource(query.toNativeUtf8());
|
||||
int resultCode = _database.prepare(queryC, -1, statementOut, nullptr);
|
||||
final statement = StatementResource(statementOut.value);
|
||||
calloc.free(statementOut);
|
||||
calloc.free(queryC);
|
||||
queryC.free();
|
||||
|
||||
if (resultCode != Errors.SQLITE_OK) {
|
||||
bindings.sqlite3_finalize(statement);
|
||||
statement.finalize();
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
|
||||
Map<String, int> columnIndices = {};
|
||||
int columnCount = bindings.sqlite3_column_count(statement);
|
||||
int columnCount = statement.columnCount;
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
String columnName =
|
||||
bindings.sqlite3_column_name(statement, i).toDartString();
|
||||
String columnName = statement.columnName(i);
|
||||
columnIndices[columnName] = i;
|
||||
}
|
||||
|
||||
return Result._(this, statement, columnIndices);
|
||||
}
|
||||
|
||||
SQLiteException _loadError(int errorCode) {
|
||||
String errorMessage = bindings.sqlite3_errmsg(_database).toDartString();
|
||||
SQLiteException _loadError([int? errorCode]) {
|
||||
String errorMessage = _database.errmsg().toDartString();
|
||||
if (errorCode == null) {
|
||||
return SQLiteException(errorMessage);
|
||||
}
|
||||
String errorCodeExplanation =
|
||||
bindings.sqlite3_errstr(errorCode).toDartString();
|
||||
return SQLiteException(
|
||||
@@ -125,7 +125,7 @@ class Result extends IterableBase<Row> implements ClosableIterable<Row> {
|
||||
|
||||
Result._(
|
||||
Database database,
|
||||
Pointer<Statement> statement,
|
||||
StatementResource statement,
|
||||
Map<String, int> columnIndices,
|
||||
) : _iterator = _ResultIterator(statement, columnIndices) {}
|
||||
|
||||
@@ -135,7 +135,7 @@ class Result extends IterableBase<Row> implements ClosableIterable<Row> {
|
||||
}
|
||||
|
||||
class _ResultIterator implements ClosableIterator<Row> {
|
||||
final Pointer<Statement> _statement;
|
||||
final StatementResource _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
Row? _currentRow;
|
||||
@@ -148,7 +148,7 @@ class _ResultIterator implements ClosableIterator<Row> {
|
||||
throw SQLiteException("The result has already been closed.");
|
||||
}
|
||||
_currentRow?._setNotCurrent();
|
||||
int stepResult = bindings.sqlite3_step(_statement);
|
||||
int stepResult = _statement.step();
|
||||
if (stepResult == Errors.SQLITE_ROW) {
|
||||
_currentRow = Row._(_statement, _columnIndices);
|
||||
return true;
|
||||
@@ -168,12 +168,12 @@ class _ResultIterator implements ClosableIterator<Row> {
|
||||
void close() {
|
||||
_currentRow?._setNotCurrent();
|
||||
_closed = true;
|
||||
bindings.sqlite3_finalize(_statement);
|
||||
_statement.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
class Row {
|
||||
final Pointer<Statement> _statement;
|
||||
final StatementResource _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
bool _isCurrentRow = true;
|
||||
@@ -201,12 +201,10 @@ class Row {
|
||||
|
||||
Type dynamicType;
|
||||
if (convert == Convert.DynamicType) {
|
||||
dynamicType =
|
||||
_typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
|
||||
dynamicType = _typeFromCode(_statement.columnType(columnIndex));
|
||||
} else {
|
||||
dynamicType = _typeFromText(bindings
|
||||
.sqlite3_column_decltype(_statement, columnIndex)
|
||||
.toDartString());
|
||||
dynamicType =
|
||||
_typeFromText(_statement.columnDecltype(columnIndex).toDartString());
|
||||
}
|
||||
|
||||
switch (dynamicType) {
|
||||
@@ -230,7 +228,7 @@ class Row {
|
||||
/// integer.
|
||||
int readColumnByIndexAsInt(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return bindings.sqlite3_column_int(_statement, columnIndex);
|
||||
return _statement.columnInt(columnIndex);
|
||||
}
|
||||
|
||||
/// Reads column [columnName] and converts to [Type.Text] if not text.
|
||||
@@ -241,7 +239,7 @@ class Row {
|
||||
/// Reads column [columnIndex] and converts to [Type.Text] if not text.
|
||||
String readColumnByIndexAsText(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return bindings.sqlite3_column_text(_statement, columnIndex).toDartString();
|
||||
return _statement.columnText(columnIndex).toDartString();
|
||||
}
|
||||
|
||||
void _checkIsCurrentRow() {
|
||||
@@ -257,6 +255,93 @@ class Row {
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseResource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer =
|
||||
NativeFinalizer(bindings.sqlite3_close_v2_native_return_void.cast());
|
||||
|
||||
/// [_statement] must never escape [StatementResource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
Pointer<types.Database> _database;
|
||||
|
||||
DatabaseResource(this._database) {
|
||||
_finalizer.attach(this, _database.cast(), detach: this);
|
||||
}
|
||||
|
||||
int close() {
|
||||
_finalizer.detach(this);
|
||||
return bindings.sqlite3_close_v2(_database);
|
||||
}
|
||||
|
||||
int prepare(Utf8Resource query, int nbytes,
|
||||
Pointer<Pointer<Statement>> statementOut, Pointer<Pointer<Utf8>> tail) {
|
||||
int result = bindings.sqlite3_prepare_v2(
|
||||
_database, query.unsafe(), nbytes, statementOut, tail);
|
||||
return result;
|
||||
}
|
||||
|
||||
Pointer<Utf8> errmsg() => bindings.sqlite3_errmsg(_database);
|
||||
}
|
||||
|
||||
class StatementResource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer =
|
||||
NativeFinalizer(bindings.sqlite3_finalize_native_return_void.cast());
|
||||
|
||||
/// [_statement] must never escape [StatementResource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
final Pointer<Statement> _statement;
|
||||
|
||||
StatementResource(this._statement) {
|
||||
_finalizer.attach(this, _statement.cast(), detach: this);
|
||||
}
|
||||
|
||||
int finalize() {
|
||||
_finalizer.detach(this);
|
||||
return bindings.sqlite3_finalize(_statement);
|
||||
}
|
||||
|
||||
int get columnCount => bindings.sqlite3_column_count(_statement);
|
||||
|
||||
String columnName(int index) =>
|
||||
bindings.sqlite3_column_name(_statement, index).toDartString();
|
||||
|
||||
int step() => bindings.sqlite3_step(_statement);
|
||||
|
||||
int columnType(int columnIndex) =>
|
||||
bindings.sqlite3_column_type(_statement, columnIndex);
|
||||
|
||||
Pointer<Utf8> columnDecltype(int columnIndex) =>
|
||||
bindings.sqlite3_column_decltype(_statement, columnIndex);
|
||||
|
||||
int columnInt(int columnIndex) =>
|
||||
bindings.sqlite3_column_int(_statement, columnIndex);
|
||||
|
||||
Pointer<Utf8> columnText(int columnIndex) =>
|
||||
bindings.sqlite3_column_text(_statement, columnIndex);
|
||||
}
|
||||
|
||||
class Utf8Resource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer = NativeFinalizer(posixFree);
|
||||
|
||||
/// [_cString] must never escape [Utf8Resource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
final Pointer<Utf8> _cString;
|
||||
|
||||
Utf8Resource(this._cString) {
|
||||
_finalizer.attach(this, _cString.cast(), detach: this);
|
||||
}
|
||||
|
||||
void free() {
|
||||
_finalizer.detach(this);
|
||||
calloc.free(_cString);
|
||||
}
|
||||
|
||||
/// Ensure this [Utf8Resource] stays in scope longer than the inner resource.
|
||||
Pointer<Utf8> unsafe() => _cString;
|
||||
}
|
||||
|
||||
final DynamicLibrary stdlib = DynamicLibrary.process();
|
||||
final posixFree = stdlib.lookup<NativeFunction<Void Function(Pointer)>>("free");
|
||||
|
||||
Type _typeFromCode(int code) {
|
||||
switch (code) {
|
||||
case Types.SQLITE_INTEGER:
|
||||
|
||||
@@ -2,10 +2,9 @@ name: sqlite3
|
||||
version: 0.0.1
|
||||
description: >-
|
||||
Sqlite3 wrapper. Demo for dart:ffi.
|
||||
author: Daco Harkes <dacoharkes@google.com>, Samir Jindel <sjindel@google.com>
|
||||
environment:
|
||||
sdk: '>=2.12.0-0 <3.0.0'
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
dependencies:
|
||||
ffi: ^1.1.2
|
||||
ffi: ^2.0.0
|
||||
dev_dependencies:
|
||||
test: ^1.16.0-nullsafety.12
|
||||
test: ^1.21.1
|
||||
|
||||
@@ -20,25 +20,10 @@ Invalid argument(s): Failed to load dynamic library (126)
|
||||
## 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.
|
||||
Running `dart 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
|
||||
$ dart run example/main
|
||||
1 Chocolade chip cookie Chocolade cookie foo
|
||||
2 Ginger cookie null 42
|
||||
3 Cinnamon roll null null
|
||||
|
||||
@@ -33,6 +33,9 @@ class _SQLiteBindings {
|
||||
int flags, Pointer<Utf8> vfs) sqlite3_open_v2;
|
||||
|
||||
int Function(Pointer<Database> database) sqlite3_close_v2;
|
||||
Pointer<NativeFunction<sqlite3_close_v2_native_t>> sqlite3_close_v2_native;
|
||||
Pointer<NativeFunction<Void Function(Pointer<Database> database)>>
|
||||
sqlite3_close_v2_native_return_void;
|
||||
|
||||
/// Compiling An SQL Statement
|
||||
///
|
||||
@@ -213,6 +216,9 @@ class _SQLiteBindings {
|
||||
/// statement after it has been finalized can result in undefined and
|
||||
/// undesirable behavior such as segfaults and heap corruption.
|
||||
int Function(Pointer<Statement> statement) sqlite3_finalize;
|
||||
Pointer<NativeFunction<sqlite3_finalize_native_t>> sqlite3_finalize_native;
|
||||
Pointer<NativeFunction<Void Function(Pointer<Statement> statement)>>
|
||||
sqlite3_finalize_native_return_void;
|
||||
|
||||
/// Number Of Columns In A Result Set
|
||||
///
|
||||
@@ -335,9 +341,10 @@ class _SQLiteBindings {
|
||||
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_close_v2_native = sqlite
|
||||
.lookup<NativeFunction<sqlite3_close_v2_native_t>>("sqlite3_close_v2");
|
||||
sqlite3_close_v2_native_return_void = sqlite3_close_v2_native.cast();
|
||||
sqlite3_close_v2 = sqlite3_close_v2_native.asFunction();
|
||||
sqlite3_prepare_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
|
||||
"sqlite3_prepare_v2")
|
||||
@@ -348,9 +355,10 @@ class _SQLiteBindings {
|
||||
sqlite3_reset = sqlite
|
||||
.lookup<NativeFunction<sqlite3_reset_native_t>>("sqlite3_reset")
|
||||
.asFunction();
|
||||
sqlite3_finalize = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>("sqlite3_finalize")
|
||||
.asFunction();
|
||||
sqlite3_finalize_native = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>("sqlite3_finalize");
|
||||
sqlite3_finalize_native_return_void = sqlite3_finalize_native.cast();
|
||||
sqlite3_finalize = sqlite3_finalize_native.asFunction();
|
||||
sqlite3_errstr = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errstr_native_t>>("sqlite3_errstr")
|
||||
.asFunction();
|
||||
|
||||
@@ -23,19 +23,19 @@ import "collections/closable_iterator.dart";
|
||||
///
|
||||
/// This database interacts with SQLite synchonously.
|
||||
class Database {
|
||||
Pointer<types.Database> _database;
|
||||
DatabaseResource _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<Pointer<types.Database>> dbOut = calloc();
|
||||
final pathC = path.toNativeUtf8();
|
||||
final pathC = Utf8Resource(path.toNativeUtf8());
|
||||
final int resultCode =
|
||||
bindings.sqlite3_open_v2(pathC, dbOut, flags, nullptr);
|
||||
_database = dbOut.value;
|
||||
bindings.sqlite3_open_v2(pathC.unsafe(), dbOut, flags, nullptr);
|
||||
_database = DatabaseResource(dbOut.value);
|
||||
calloc.free(dbOut);
|
||||
calloc.free(pathC);
|
||||
pathC.free();
|
||||
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = true;
|
||||
@@ -55,7 +55,7 @@ class Database {
|
||||
/// avoid resource leaks.
|
||||
void close() {
|
||||
assert(_open);
|
||||
final int resultCode = bindings.sqlite3_close_v2(_database);
|
||||
final int resultCode = _database.close();
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = false;
|
||||
} else {
|
||||
@@ -65,18 +65,17 @@ class Database {
|
||||
|
||||
/// Execute a query, discarding any returned rows.
|
||||
void execute(String query) {
|
||||
Pointer<Pointer<Statement>> statementOut = calloc();
|
||||
Pointer<Utf8> queryC = query.toNativeUtf8();
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, nullptr);
|
||||
Pointer<Statement> statement = statementOut.value;
|
||||
Pointer<Pointer<Statement>> statementOut = malloc();
|
||||
final queryC = Utf8Resource(query.toNativeUtf8());
|
||||
int resultCode = _database.prepare(queryC, -1, statementOut, nullptr);
|
||||
final statement = StatementResource(statementOut.value);
|
||||
calloc.free(statementOut);
|
||||
calloc.free(queryC);
|
||||
queryC.free();
|
||||
|
||||
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
|
||||
resultCode = bindings.sqlite3_step(statement);
|
||||
resultCode = statement.step();
|
||||
}
|
||||
bindings.sqlite3_finalize(statement);
|
||||
statement.finalize();
|
||||
if (resultCode != Errors.SQLITE_DONE) {
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
@@ -84,24 +83,22 @@ class Database {
|
||||
|
||||
/// Evaluate a query and return the resulting rows as an iterable.
|
||||
Result query(String query) {
|
||||
Pointer<Pointer<Statement>> statementOut = calloc();
|
||||
Pointer<Utf8> queryC = query.toNativeUtf8();
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, nullptr);
|
||||
Pointer<Statement> statement = statementOut.value;
|
||||
Pointer<Pointer<Statement>> statementOut = malloc();
|
||||
final queryC = Utf8Resource(query.toNativeUtf8());
|
||||
int resultCode = _database.prepare(queryC, -1, statementOut, nullptr);
|
||||
final statement = StatementResource(statementOut.value);
|
||||
calloc.free(statementOut);
|
||||
calloc.free(queryC);
|
||||
queryC.free();
|
||||
|
||||
if (resultCode != Errors.SQLITE_OK) {
|
||||
bindings.sqlite3_finalize(statement);
|
||||
statement.finalize();
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
|
||||
Map<String, int> columnIndices = {};
|
||||
int columnCount = bindings.sqlite3_column_count(statement);
|
||||
int columnCount = statement.columnCount;
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
String columnName =
|
||||
bindings.sqlite3_column_name(statement, i).toDartString();
|
||||
String columnName = statement.columnName(i);
|
||||
columnIndices[columnName] = i;
|
||||
}
|
||||
|
||||
@@ -109,7 +106,7 @@ class Database {
|
||||
}
|
||||
|
||||
SQLiteException _loadError([int errorCode]) {
|
||||
String errorMessage = bindings.sqlite3_errmsg(_database).toDartString();
|
||||
String errorMessage = _database.errmsg().toDartString();
|
||||
if (errorCode == null) {
|
||||
return SQLiteException(errorMessage);
|
||||
}
|
||||
@@ -126,18 +123,13 @@ class Database {
|
||||
/// 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 Pointer<Statement> _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
Row _currentRow = null;
|
||||
|
||||
Result._(
|
||||
this._database,
|
||||
this._statement,
|
||||
this._columnIndices,
|
||||
) : _iterator = _ResultIterator(_statement, _columnIndices) {}
|
||||
Database database,
|
||||
StatementResource statement,
|
||||
Map<String, int> columnIndices,
|
||||
) : _iterator = _ResultIterator(statement, columnIndices) {}
|
||||
|
||||
void close() => _iterator.close();
|
||||
|
||||
@@ -145,10 +137,10 @@ class Result extends IterableBase<Row> implements ClosableIterable<Row> {
|
||||
}
|
||||
|
||||
class _ResultIterator implements ClosableIterator<Row> {
|
||||
final Pointer<Statement> _statement;
|
||||
final StatementResource _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
Row _currentRow = null;
|
||||
Row _currentRow;
|
||||
bool _closed = false;
|
||||
|
||||
_ResultIterator(this._statement, this._columnIndices) {}
|
||||
@@ -157,8 +149,10 @@ class _ResultIterator implements ClosableIterator<Row> {
|
||||
if (_closed) {
|
||||
throw SQLiteException("The result has already been closed.");
|
||||
}
|
||||
_currentRow?._setNotCurrent();
|
||||
int stepResult = bindings.sqlite3_step(_statement);
|
||||
if (_currentRow != null) {
|
||||
_currentRow._setNotCurrent();
|
||||
}
|
||||
int stepResult = _statement.step();
|
||||
if (stepResult == Errors.SQLITE_ROW) {
|
||||
_currentRow = Row._(_statement, _columnIndices);
|
||||
return true;
|
||||
@@ -176,14 +170,14 @@ class _ResultIterator implements ClosableIterator<Row> {
|
||||
}
|
||||
|
||||
void close() {
|
||||
_currentRow?._setNotCurrent();
|
||||
_currentRow._setNotCurrent();
|
||||
_closed = true;
|
||||
bindings.sqlite3_finalize(_statement);
|
||||
_statement.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
class Row {
|
||||
final Pointer<Statement> _statement;
|
||||
final StatementResource _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
bool _isCurrentRow = true;
|
||||
@@ -211,12 +205,10 @@ class Row {
|
||||
|
||||
Type dynamicType;
|
||||
if (convert == Convert.DynamicType) {
|
||||
dynamicType =
|
||||
_typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
|
||||
dynamicType = _typeFromCode(_statement.columnType(columnIndex));
|
||||
} else {
|
||||
dynamicType = _typeFromText(bindings
|
||||
.sqlite3_column_decltype(_statement, columnIndex)
|
||||
.toDartString());
|
||||
dynamicType =
|
||||
_typeFromText(_statement.columnDecltype(columnIndex).toDartString());
|
||||
}
|
||||
|
||||
switch (dynamicType) {
|
||||
@@ -226,7 +218,6 @@ class Row {
|
||||
return readColumnByIndexAsText(columnIndex);
|
||||
case Type.Null:
|
||||
return null;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -241,7 +232,7 @@ class Row {
|
||||
/// integer.
|
||||
int readColumnByIndexAsInt(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return bindings.sqlite3_column_int(_statement, columnIndex);
|
||||
return _statement.columnInt(columnIndex);
|
||||
}
|
||||
|
||||
/// Reads column [columnName] and converts to [Type.Text] if not text.
|
||||
@@ -252,7 +243,7 @@ class Row {
|
||||
/// Reads column [columnIndex] and converts to [Type.Text] if not text.
|
||||
String readColumnByIndexAsText(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return bindings.sqlite3_column_text(_statement, columnIndex).toDartString();
|
||||
return _statement.columnText(columnIndex).toDartString();
|
||||
}
|
||||
|
||||
void _checkIsCurrentRow() {
|
||||
@@ -268,6 +259,93 @@ class Row {
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseResource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer =
|
||||
NativeFinalizer(bindings.sqlite3_close_v2_native_return_void.cast());
|
||||
|
||||
/// [_statement] must never escape [StatementResource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
Pointer<types.Database> _database;
|
||||
|
||||
DatabaseResource(this._database) {
|
||||
_finalizer.attach(this, _database.cast(), detach: this);
|
||||
}
|
||||
|
||||
int close() {
|
||||
_finalizer.detach(this);
|
||||
return bindings.sqlite3_close_v2(_database);
|
||||
}
|
||||
|
||||
int prepare(Utf8Resource query, int nbytes,
|
||||
Pointer<Pointer<Statement>> statementOut, Pointer<Pointer<Utf8>> tail) {
|
||||
int result = bindings.sqlite3_prepare_v2(
|
||||
_database, query.unsafe(), nbytes, statementOut, tail);
|
||||
return result;
|
||||
}
|
||||
|
||||
Pointer<Utf8> errmsg() => bindings.sqlite3_errmsg(_database);
|
||||
}
|
||||
|
||||
class StatementResource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer =
|
||||
NativeFinalizer(bindings.sqlite3_finalize_native_return_void.cast());
|
||||
|
||||
/// [_statement] must never escape [StatementResource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
final Pointer<Statement> _statement;
|
||||
|
||||
StatementResource(this._statement) {
|
||||
_finalizer.attach(this, _statement.cast(), detach: this);
|
||||
}
|
||||
|
||||
int finalize() {
|
||||
_finalizer.detach(this);
|
||||
return bindings.sqlite3_finalize(_statement);
|
||||
}
|
||||
|
||||
int get columnCount => bindings.sqlite3_column_count(_statement);
|
||||
|
||||
String columnName(int index) =>
|
||||
bindings.sqlite3_column_name(_statement, index).toDartString();
|
||||
|
||||
int step() => bindings.sqlite3_step(_statement);
|
||||
|
||||
int columnType(int columnIndex) =>
|
||||
bindings.sqlite3_column_type(_statement, columnIndex);
|
||||
|
||||
Pointer<Utf8> columnDecltype(int columnIndex) =>
|
||||
bindings.sqlite3_column_decltype(_statement, columnIndex);
|
||||
|
||||
int columnInt(int columnIndex) =>
|
||||
bindings.sqlite3_column_int(_statement, columnIndex);
|
||||
|
||||
Pointer<Utf8> columnText(int columnIndex) =>
|
||||
bindings.sqlite3_column_text(_statement, columnIndex);
|
||||
}
|
||||
|
||||
class Utf8Resource implements Finalizable {
|
||||
static final NativeFinalizer _finalizer = NativeFinalizer(posixFree);
|
||||
|
||||
/// [_cString] must never escape [Utf8Resource], otherwise the
|
||||
/// [_finalizer] will run prematurely.
|
||||
final Pointer<Utf8> _cString;
|
||||
|
||||
Utf8Resource(this._cString) {
|
||||
_finalizer.attach(this, _cString.cast(), detach: this);
|
||||
}
|
||||
|
||||
void free() {
|
||||
_finalizer.detach(this);
|
||||
calloc.free(_cString);
|
||||
}
|
||||
|
||||
/// Ensure this [Utf8Resource] stays in scope longer than the inner resource.
|
||||
Pointer<Utf8> unsafe() => _cString;
|
||||
}
|
||||
|
||||
final DynamicLibrary stdlib = DynamicLibrary.process();
|
||||
final posixFree = stdlib.lookup<NativeFunction<Void Function(Pointer)>>("free");
|
||||
|
||||
Type _typeFromCode(int code) {
|
||||
switch (code) {
|
||||
case Types.SQLITE_INTEGER:
|
||||
@@ -297,7 +375,6 @@ Type _typeFromText(String textRepresentation) {
|
||||
case "null":
|
||||
return Type.Null;
|
||||
}
|
||||
if (textRepresentation == null) return Type.Null;
|
||||
throw Exception("Unknown type [$textRepresentation]");
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
String _platformPath(String name, {String path}) {
|
||||
if (path == null) path = "";
|
||||
String _platformPath(String name, String path) {
|
||||
if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia)
|
||||
return path + "lib" + name + ".so";
|
||||
if (Platform.isMacOS) return path + "lib" + name + ".dylib";
|
||||
@@ -16,7 +15,7 @@ String _platformPath(String name, {String path}) {
|
||||
throw Exception("Platform not implemented");
|
||||
}
|
||||
|
||||
DynamicLibrary dlopenPlatformSpecific(String name, {String path}) {
|
||||
String fullPath = _platformPath(name, path: path);
|
||||
DynamicLibrary dlopenPlatformSpecific(String name, {String path = ""}) {
|
||||
String fullPath = _platformPath(name, path);
|
||||
return DynamicLibrary.open(fullPath);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@ name: sqlite3
|
||||
version: 0.0.1
|
||||
description: >-
|
||||
Sqlite3 wrapper. Demo for dart:ffi.
|
||||
author: Daco Harkes <dacoharkes@google.com>, Samir Jindel <sjindel@google.com>
|
||||
environment:
|
||||
sdk: '>=2.1.0 <3.0.0'
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
dependencies:
|
||||
ffi: ^1.1.2
|
||||
ffi: ^2.0.0
|
||||
dev_dependencies:
|
||||
test: ^1.5.3
|
||||
test: ^1.21.1
|
||||
|
||||
@@ -49,8 +49,7 @@ void main() {
|
||||
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);
|
||||
final alternativeName = r.readColumn("alternative_name") as String;
|
||||
dynamic multiTypedValue = r.readColumn("multi_typed_column");
|
||||
expect(
|
||||
true,
|
||||
@@ -76,8 +75,7 @@ void main() {
|
||||
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);
|
||||
final alternativeName = r.readColumn("alternative_name") as String;
|
||||
dynamic multiTypedValue = r.readColumn("multi_typed_column");
|
||||
expect(
|
||||
true,
|
||||
|
||||
Reference in New Issue
Block a user