[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:
Daco Harkes
2022-06-07 12:11:39 +00:00
committed by Commit Bot
parent 79abd5ea56
commit 81584f5d03
10 changed files with 295 additions and 150 deletions
+2 -17
View File
@@ -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();
+123 -38
View File
@@ -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:
+3 -4
View File
@@ -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
+2 -17
View File
@@ -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();
+127 -50
View File
@@ -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);
}
+3 -4
View File
@@ -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
+2 -4
View File
@@ -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,