Skip to content

Commit 7023618

Browse files
committed
Native finalizer for db and stmt
1 parent 85d7325 commit 7023618

File tree

8 files changed

+69
-38
lines changed

8 files changed

+69
-38
lines changed

sqlite3/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- You should drop your dependencies on `sqlite3_flutter_libs` and
77
`sqlcipher_flutter_libs` when upgrading.
88
- __Breaking change__: Parameters to `SqliteException`s are now named.
9+
- Use native finalizers to clear statements, parameters and sessions. This makes
10+
closing databases after e.g. a reload much more efficient.
911

1012
## 2.9.4
1113

sqlite3/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ For native platforms, the basic sketch for using this library is to:
1919
open a temporary in-memory database.
2020
3. Use `Database.execute` or `Database.prepare` to execute statements directly
2121
or by preparing them first.
22-
4. Don't forget to close prepared statements or the database with `dispose()`
23-
once you no longer need them.
2422

2523
For a more complete example on how to use this library, see the [example](https://pub.dev/packages/sqlite3/example).
2624

sqlite3/lib/src/ffi/bindings.dart

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ final supportsPrepareV3 = sqlite3_libversion_number() >= _firstVersionForV3;
3434
final supportsErrorOffset =
3535
sqlite3_libversion_number() >= _firstVersionForErrorOffset;
3636

37+
final databaseFinalizer = NativeFinalizer(addresses.sqlite3_close_v2.cast());
38+
final statementFinalizer = NativeFinalizer(addresses.sqlite3_finalize.cast());
3739
final sessionDeleteFinalizer = NativeFinalizer(
3840
addresses.sqlite3session_delete.cast(),
3941
);
@@ -208,7 +210,7 @@ final class FfiBindings implements RawSqliteBindings {
208210
}
209211

210212
@override
211-
SqliteResult<RawSqliteDatabase> sqlite3_open_v2(
213+
SqliteResult<RawSqliteDatabase?> sqlite3_open_v2(
212214
String name,
213215
int flags,
214216
String? zVfs,
@@ -225,7 +227,10 @@ final class FfiBindings implements RawSqliteBindings {
225227
flags,
226228
vfsPtr,
227229
);
228-
final result = SqliteResult(resultCode, FfiDatabase(outDb.value));
230+
final result = (
231+
resultCode: resultCode,
232+
result: outDb.isNullPointer ? null : FfiDatabase(outDb.value),
233+
);
229234

230235
namePtr.free();
231236
outDb.free();
@@ -740,7 +745,10 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
740745
final value = outValue.value;
741746
outValue.free();
742747

743-
return SqliteResult(result, value.isNullPointer ? null : FfiValue(value));
748+
return (
749+
resultCode: result,
750+
result: value.isNullPointer ? null : FfiValue(value),
751+
);
744752
}
745753

746754
@override
@@ -759,7 +767,10 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
759767
final value = outValue.value;
760768
outValue.free();
761769

762-
return SqliteResult(result, value.isNullPointer ? null : FfiValue(value));
770+
return (
771+
resultCode: result,
772+
result: value.isNullPointer ? null : FfiValue(value),
773+
);
763774
}
764775

765776
@override
@@ -800,17 +811,21 @@ final class FfiChangesetIterator implements RawChangesetIterator, Finalizable {
800811
}
801812
}
802813

803-
final class FfiDatabase implements RawSqliteDatabase {
814+
final class FfiDatabase implements RawSqliteDatabase, Finalizable {
804815
final Pointer<sqlite3> db;
816+
final Object detachToken = Object();
805817

806818
NativeCallable<_UpdateHook>? _installedUpdateHook;
807819
NativeCallable<_CommitHook>? _installedCommitHook;
808820
NativeCallable<_RollbackHook>? _installedRollbackHook;
809821

810-
FfiDatabase(this.db);
822+
FfiDatabase(this.db) {
823+
databaseFinalizer.attach(this, db.cast(), detach: detachToken);
824+
}
811825

812826
@override
813827
int sqlite3_close_v2() {
828+
databaseFinalizer.detach(detachToken);
814829
return libsqlite3.sqlite3_close_v2(db);
815830
}
816831

@@ -1085,16 +1100,19 @@ final class FfiStatementCompiler implements RawStatementCompiler {
10851100
final stmt = stmtOut.value;
10861101
final libraryStatement = stmt.isNullPointer ? null : FfiStatement(stmt);
10871102

1088-
return SqliteResult(result, libraryStatement);
1103+
return (resultCode: result, result: libraryStatement);
10891104
}
10901105
}
10911106

1092-
final class FfiStatement implements RawSqliteStatement {
1107+
final class FfiStatement implements RawSqliteStatement, Finalizable {
10931108
final Pointer<sqlite3_stmt> stmt;
1109+
final Object detachToken = Object();
10941110

10951111
final List<Pointer> _allocatedArguments = [];
10961112

1097-
FfiStatement(this.stmt);
1113+
FfiStatement(this.stmt) {
1114+
statementFinalizer.attach(this, stmt.cast(), detach: detachToken);
1115+
}
10981116

10991117
@visibleForTesting
11001118
List<Pointer> get allocatedArguments => _allocatedArguments;
@@ -1238,6 +1256,7 @@ final class FfiStatement implements RawSqliteStatement {
12381256

12391257
@override
12401258
void sqlite3_finalize() {
1259+
statementFinalizer.detach(detachToken);
12411260
libsqlite3.sqlite3_finalize(stmt);
12421261
}
12431262

sqlite3/lib/src/ffi/libsqlite3.g.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,10 @@ class _SymbolAddresses {
866866
const _SymbolAddresses();
867867
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>
868868
get sqlite3_free => ffi.Native.addressOf(self.sqlite3_free);
869+
ffi.Pointer<ffi.NativeFunction<ffi.Int Function(ffi.Pointer<sqlite3>)>>
870+
get sqlite3_close_v2 => ffi.Native.addressOf(self.sqlite3_close_v2);
871+
ffi.Pointer<ffi.NativeFunction<ffi.Int Function(ffi.Pointer<sqlite3_stmt>)>>
872+
get sqlite3_finalize => ffi.Native.addressOf(self.sqlite3_finalize);
869873
ffi.Pointer<
870874
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<sqlite3_session>)>
871875
>

sqlite3/lib/src/implementation/bindings.dart

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ abstract interface class RawSqliteBindings {
116116

117117
String? sqlite3_temp_directory;
118118

119-
SqliteResult<RawSqliteDatabase> sqlite3_open_v2(
119+
SqliteResult<RawSqliteDatabase?> sqlite3_open_v2(
120120
String name,
121121
int flags,
122122
String? zVfs,
@@ -166,15 +166,9 @@ abstract interface class RawSqliteSession {
166166
}
167167

168168
/// Combines a sqlite result code and the result object.
169-
final class SqliteResult<T> {
170-
final int resultCode;
171-
172-
/// The result of the operation, which is assumed to be valid if [resultCode]
173-
/// is zero.
174-
final T result;
175-
176-
SqliteResult(this.resultCode, this.result);
177-
}
169+
///
170+
/// The result is only assumed to be valid if the result code is zero.
171+
typedef SqliteResult<T> = ({T result, int resultCode});
178172

179173
typedef RawXFunc = void Function(RawSqliteContext, List<RawSqliteValue>);
180174
typedef RawXStep = void Function(RawSqliteContext, List<RawSqliteValue>);

sqlite3/lib/src/implementation/sqlite3.dart

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,28 @@ base class Sqlite3Implementation implements CommonSqlite3 {
7070

7171
final result = bindings.sqlite3_open_v2(filename, flags, vfs);
7272
if (result.resultCode != SqlError.SQLITE_OK) {
73-
final exception = createExceptionRaw(
74-
bindings,
75-
result.result,
76-
result.resultCode,
77-
operation: 'opening the database',
78-
);
79-
// Close the database after creating the exception, which needs to read
80-
// the extended error from the database.
81-
result.result.sqlite3_close_v2();
82-
throw exception;
73+
if (result.result case final db?) {
74+
// Even with an opening error, we usually still get a database (the only
75+
// exception is if SQLite can't allocate the db structure).
76+
final exception = createExceptionRaw(
77+
bindings,
78+
db,
79+
result.resultCode,
80+
operation: 'opening the database',
81+
);
82+
// Close the database after creating the exception, which needs to read
83+
// the extended error from the database.
84+
db.sqlite3_close_v2();
85+
throw exception;
86+
} else {
87+
throw SqliteException(
88+
extendedResultCode: result.resultCode,
89+
message: 'Could not open database',
90+
);
91+
}
8392
}
8493

85-
return wrapDatabase(result.result..sqlite3_extended_result_codes(1));
94+
return wrapDatabase(result.result!..sqlite3_extended_result_codes(1));
8695
}
8796

8897
@override

sqlite3/lib/src/wasm/bindings.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ final class WasmSqliteBindings implements RawSqliteBindings {
5656
}
5757

5858
@override
59-
SqliteResult<RawSqliteDatabase> sqlite3_open_v2(
59+
SqliteResult<RawSqliteDatabase?> sqlite3_open_v2(
6060
String name,
6161
int flags,
6262
String? zVfs,
@@ -74,7 +74,10 @@ final class WasmSqliteBindings implements RawSqliteBindings {
7474
..free(vfsPtr);
7575
if (zVfs != null) bindings.free(vfsPtr);
7676

77-
return SqliteResult(result, WasmDatabase(bindings, dbPtr));
77+
return (
78+
resultCode: result,
79+
result: dbPtr != 0 ? WasmDatabase(bindings, dbPtr) : null,
80+
);
7881
}
7982

8083
@override
@@ -443,7 +446,7 @@ final class WasmStatementCompiler implements RawStatementCompiler {
443446
final stmt = database.bindings.memory.int32ValueOfPointer(stmtOut);
444447
final libraryStatement = stmt == 0 ? null : WasmStatement(database, stmt);
445448

446-
return SqliteResult(result, libraryStatement);
449+
return (resultCode: result, result: libraryStatement);
447450
}
448451
}
449452

@@ -921,9 +924,9 @@ final class WasmChangesetIterator implements RawChangesetIterator {
921924
final value = _bindings.memory.int32ValueOfPointer(outValue);
922925
_bindings.free(outValue);
923926

924-
return SqliteResult(
925-
resultCode,
926-
value != 0 ? WasmValue(_bindings, value) : null,
927+
return (
928+
resultCode: resultCode,
929+
result: value != 0 ? WasmValue(_bindings, value) : null,
927930
);
928931
}
929932

sqlite3/tool/generate_bindings.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ DeclarationFilters _includeSqlite3Only = DeclarationFilters(
4646
shouldInclude: (d) => d.isSqlite3Symbol,
4747
shouldIncludeSymbolAddress: (decl) {
4848
return switch (decl.originalName) {
49+
'sqlite3_close_v2' => true,
50+
'sqlite3_finalize' => true,
4951
'sqlite3changeset_finalize' => true,
5052
'sqlite3session_delete' => true,
5153
'sqlite3_free' => true,

0 commit comments

Comments
 (0)