diff --git a/samples/ffi/sqlite/README.md b/samples/ffi/sqlite/README.md index fbd5cb93f4e..c4656df00a4 100644 --- a/samples/ffi/sqlite/README.md +++ b/samples/ffi/sqlite/README.md @@ -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 diff --git a/samples/ffi/sqlite/lib/src/bindings/bindings.dart b/samples/ffi/sqlite/lib/src/bindings/bindings.dart index a3d73bdaa5a..c262829f05a 100644 --- a/samples/ffi/sqlite/lib/src/bindings/bindings.dart +++ b/samples/ffi/sqlite/lib/src/bindings/bindings.dart @@ -34,6 +34,10 @@ class _SQLiteBindings { Pointer vfs) sqlite3_open_v2; late int Function(Pointer database) sqlite3_close_v2; + late Pointer> + sqlite3_close_v2_native; + late Pointer 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) sqlite3_finalize; + late Pointer> + sqlite3_finalize_native; + late Pointer statement)>> + sqlite3_finalize_native_return_void; /// Number Of Columns In A Result Set /// @@ -336,9 +344,10 @@ class _SQLiteBindings { sqlite3_open_v2 = sqlite .lookup>("sqlite3_open_v2") .asFunction(); - sqlite3_close_v2 = sqlite - .lookup>("sqlite3_close_v2") - .asFunction(); + sqlite3_close_v2_native = sqlite + .lookup>("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>( "sqlite3_prepare_v2") @@ -349,9 +358,10 @@ class _SQLiteBindings { sqlite3_reset = sqlite .lookup>("sqlite3_reset") .asFunction(); - sqlite3_finalize = sqlite - .lookup>("sqlite3_finalize") - .asFunction(); + sqlite3_finalize_native = sqlite + .lookup>("sqlite3_finalize"); + sqlite3_finalize_native_return_void = sqlite3_finalize_native.cast(); + sqlite3_finalize = sqlite3_finalize_native.asFunction(); sqlite3_errstr = sqlite .lookup>("sqlite3_errstr") .asFunction(); diff --git a/samples/ffi/sqlite/lib/src/database.dart b/samples/ffi/sqlite/lib/src/database.dart index 9b0570c4c3e..fa6a8d5745a 100644 --- a/samples/ffi/sqlite/lib/src/database.dart +++ b/samples/ffi/sqlite/lib/src/database.dart @@ -21,19 +21,19 @@ import "collections/closable_iterator.dart"; /// /// This database interacts with SQLite synchonously. class Database { - late Pointer _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> 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> statementOut = calloc(); - Pointer queryC = query.toNativeUtf8(); - int resultCode = bindings.sqlite3_prepare_v2( - _database, queryC, -1, statementOut, nullptr); - Pointer statement = statementOut.value; + Pointer> 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> statementOut = calloc(); - Pointer queryC = query.toNativeUtf8(); - int resultCode = bindings.sqlite3_prepare_v2( - _database, queryC, -1, statementOut, nullptr); - Pointer statement = statementOut.value; + Pointer> 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 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 implements ClosableIterable { Result._( Database database, - Pointer statement, + StatementResource statement, Map columnIndices, ) : _iterator = _ResultIterator(statement, columnIndices) {} @@ -135,7 +135,7 @@ class Result extends IterableBase implements ClosableIterable { } class _ResultIterator implements ClosableIterator { - final Pointer _statement; + final StatementResource _statement; final Map _columnIndices; Row? _currentRow; @@ -148,7 +148,7 @@ class _ResultIterator implements ClosableIterator { 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 { void close() { _currentRow?._setNotCurrent(); _closed = true; - bindings.sqlite3_finalize(_statement); + _statement.finalize(); } } class Row { - final Pointer _statement; + final StatementResource _statement; final Map _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 _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> statementOut, Pointer> tail) { + int result = bindings.sqlite3_prepare_v2( + _database, query.unsafe(), nbytes, statementOut, tail); + return result; + } + + Pointer 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; + + 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 columnDecltype(int columnIndex) => + bindings.sqlite3_column_decltype(_statement, columnIndex); + + int columnInt(int columnIndex) => + bindings.sqlite3_column_int(_statement, columnIndex); + + Pointer 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 _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 unsafe() => _cString; +} + +final DynamicLibrary stdlib = DynamicLibrary.process(); +final posixFree = stdlib.lookup>("free"); + Type _typeFromCode(int code) { switch (code) { case Types.SQLITE_INTEGER: diff --git a/samples/ffi/sqlite/pubspec.yaml b/samples/ffi/sqlite/pubspec.yaml index 90e67f4679c..ab886aa06b5 100644 --- a/samples/ffi/sqlite/pubspec.yaml +++ b/samples/ffi/sqlite/pubspec.yaml @@ -2,10 +2,9 @@ name: sqlite3 version: 0.0.1 description: >- Sqlite3 wrapper. Demo for dart:ffi. -author: Daco Harkes , Samir Jindel 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 diff --git a/samples_2/ffi/sqlite/README.md b/samples_2/ffi/sqlite/README.md index fbd5cb93f4e..c4656df00a4 100644 --- a/samples_2/ffi/sqlite/README.md +++ b/samples_2/ffi/sqlite/README.md @@ -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 diff --git a/samples_2/ffi/sqlite/lib/src/bindings/bindings.dart b/samples_2/ffi/sqlite/lib/src/bindings/bindings.dart index a1cdba747bb..24918131e25 100644 --- a/samples_2/ffi/sqlite/lib/src/bindings/bindings.dart +++ b/samples_2/ffi/sqlite/lib/src/bindings/bindings.dart @@ -33,6 +33,9 @@ class _SQLiteBindings { int flags, Pointer vfs) sqlite3_open_v2; int Function(Pointer database) sqlite3_close_v2; + Pointer> sqlite3_close_v2_native; + Pointer 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) sqlite3_finalize; + Pointer> sqlite3_finalize_native; + Pointer statement)>> + sqlite3_finalize_native_return_void; /// Number Of Columns In A Result Set /// @@ -335,9 +341,10 @@ class _SQLiteBindings { sqlite3_open_v2 = sqlite .lookup>("sqlite3_open_v2") .asFunction(); - sqlite3_close_v2 = sqlite - .lookup>("sqlite3_close_v2") - .asFunction(); + sqlite3_close_v2_native = sqlite + .lookup>("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>( "sqlite3_prepare_v2") @@ -348,9 +355,10 @@ class _SQLiteBindings { sqlite3_reset = sqlite .lookup>("sqlite3_reset") .asFunction(); - sqlite3_finalize = sqlite - .lookup>("sqlite3_finalize") - .asFunction(); + sqlite3_finalize_native = sqlite + .lookup>("sqlite3_finalize"); + sqlite3_finalize_native_return_void = sqlite3_finalize_native.cast(); + sqlite3_finalize = sqlite3_finalize_native.asFunction(); sqlite3_errstr = sqlite .lookup>("sqlite3_errstr") .asFunction(); diff --git a/samples_2/ffi/sqlite/lib/src/database.dart b/samples_2/ffi/sqlite/lib/src/database.dart index d65724fd8fa..5bfc770cf12 100644 --- a/samples_2/ffi/sqlite/lib/src/database.dart +++ b/samples_2/ffi/sqlite/lib/src/database.dart @@ -23,19 +23,19 @@ import "collections/closable_iterator.dart"; /// /// This database interacts with SQLite synchonously. class Database { - Pointer _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> 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> statementOut = calloc(); - Pointer queryC = query.toNativeUtf8(); - int resultCode = bindings.sqlite3_prepare_v2( - _database, queryC, -1, statementOut, nullptr); - Pointer statement = statementOut.value; + Pointer> 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> statementOut = calloc(); - Pointer queryC = query.toNativeUtf8(); - int resultCode = bindings.sqlite3_prepare_v2( - _database, queryC, -1, statementOut, nullptr); - Pointer statement = statementOut.value; + Pointer> 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 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 implements ClosableIterable { - final Database _database; final ClosableIterator _iterator; - final Pointer _statement; - final Map _columnIndices; - - Row _currentRow = null; Result._( - this._database, - this._statement, - this._columnIndices, - ) : _iterator = _ResultIterator(_statement, _columnIndices) {} + Database database, + StatementResource statement, + Map columnIndices, + ) : _iterator = _ResultIterator(statement, columnIndices) {} void close() => _iterator.close(); @@ -145,10 +137,10 @@ class Result extends IterableBase implements ClosableIterable { } class _ResultIterator implements ClosableIterator { - final Pointer _statement; + final StatementResource _statement; final Map _columnIndices; - Row _currentRow = null; + Row _currentRow; bool _closed = false; _ResultIterator(this._statement, this._columnIndices) {} @@ -157,8 +149,10 @@ class _ResultIterator implements ClosableIterator { 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 { } void close() { - _currentRow?._setNotCurrent(); + _currentRow._setNotCurrent(); _closed = true; - bindings.sqlite3_finalize(_statement); + _statement.finalize(); } } class Row { - final Pointer _statement; + final StatementResource _statement; final Map _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 _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> statementOut, Pointer> tail) { + int result = bindings.sqlite3_prepare_v2( + _database, query.unsafe(), nbytes, statementOut, tail); + return result; + } + + Pointer 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; + + 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 columnDecltype(int columnIndex) => + bindings.sqlite3_column_decltype(_statement, columnIndex); + + int columnInt(int columnIndex) => + bindings.sqlite3_column_int(_statement, columnIndex); + + Pointer 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 _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 unsafe() => _cString; +} + +final DynamicLibrary stdlib = DynamicLibrary.process(); +final posixFree = stdlib.lookup>("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]"); } diff --git a/samples_2/ffi/sqlite/lib/src/ffi/dylib_utils.dart b/samples_2/ffi/sqlite/lib/src/ffi/dylib_utils.dart index b783ad8667f..fccdca36451 100644 --- a/samples_2/ffi/sqlite/lib/src/ffi/dylib_utils.dart +++ b/samples_2/ffi/sqlite/lib/src/ffi/dylib_utils.dart @@ -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); } diff --git a/samples_2/ffi/sqlite/pubspec.yaml b/samples_2/ffi/sqlite/pubspec.yaml index 762107fd2c2..ab886aa06b5 100644 --- a/samples_2/ffi/sqlite/pubspec.yaml +++ b/samples_2/ffi/sqlite/pubspec.yaml @@ -2,10 +2,9 @@ name: sqlite3 version: 0.0.1 description: >- Sqlite3 wrapper. Demo for dart:ffi. -author: Daco Harkes , Samir Jindel 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 diff --git a/samples_2/ffi/sqlite/test/sqlite_test.dart b/samples_2/ffi/sqlite/test/sqlite_test.dart index 91efe13cdcf..8640438b1a6 100644 --- a/samples_2/ffi/sqlite/test/sqlite_test.dart +++ b/samples_2/ffi/sqlite/test/sqlite_test.dart @@ -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,