[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
|
||||
|
||||
Reference in New Issue
Block a user