Skip to content

Commit a441069

Browse files
committed
Merge branch 'dispose-database-tracker' into develop
2 parents fcb7d78 + 919c11d commit a441069

File tree

4 files changed

+121
-12
lines changed

4 files changed

+121
-12
lines changed

drift/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
JSON.
88
- Add `runWithInterceptor` method to databases to only apply interceptors in
99
a restricted block.
10+
- Add `dispose()` method to `DatabaseTracker` to explicitly release in-memory SQLite resources.
1011

1112
## 2.23.1
1213

drift/lib/native.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,12 @@ class _NativeDelegate extends Sqlite3Delegate<Database> {
356356
}
357357

358358
db = sqlite3.open(file.path);
359-
try {
360-
tracker.markOpened(file.path, db);
361-
} on SqliteException {
362-
// ignore
359+
if (!tracker.isDisposed) {
360+
try {
361+
tracker.markOpened(file.path, db);
362+
} on SqliteException {
363+
// ignore
364+
}
363365
}
364366
} else {
365367
db = sqlite3.openInMemory();
@@ -399,10 +401,12 @@ class _NativeDelegate extends Sqlite3Delegate<Database> {
399401
await super.close();
400402

401403
if (closeUnderlyingWhenClosed) {
402-
try {
403-
tracker.markClosed(database);
404-
} on SqliteException {
405-
// ignore
404+
if (!tracker.isDisposed) {
405+
try {
406+
tracker.markClosed(database);
407+
} on SqliteException {
408+
// ignore
409+
}
406410
}
407411

408412
database.dispose();

drift/lib/src/sqlite3/database_tracker.dart

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ final DatabaseTracker tracker = DatabaseTracker();
2323
/// this is necessary.
2424
class DatabaseTracker {
2525
final Database _db;
26+
bool _isDisposed = false;
27+
28+
/// Whether this [DatabaseTracker] has been disposed.
29+
bool get isDisposed => _isDisposed;
2630

2731
/// Creates a new tracker with necessary tables.
2832
DatabaseTracker()
@@ -38,24 +42,35 @@ CREATE TABLE IF NOT EXISTS open_connections(
3842
''');
3943
}
4044

41-
/// Tracks the [openedDb]. The [path] argument can be used to track the path
42-
/// of that database, if it's bound to a file.
45+
/// Tracks the [openedDb]. The [path] argument can be used to track
46+
/// the path of that database, if it's bound to a file.
47+
///
48+
/// Throws a [StateError] if this tracker has already been disposed.
4349
void markOpened(String path, Database openedDb) {
50+
_checkIfDisposed();
51+
4452
final stmt = _db.prepare('INSERT INTO open_connections VALUES (?, ?)');
4553
stmt.execute([openedDb.handle.address, path]);
4654
stmt.dispose();
4755
}
4856

4957
/// Marks the database [db] as closed.
58+
///
59+
/// Throws a [StateError] if this tracker has already been disposed.
5060
void markClosed(Database db) {
61+
_checkIfDisposed();
62+
5163
final ptr = db.handle.address;
5264
_db.execute('DELETE FROM open_connections WHERE database_pointer = $ptr');
5365
}
5466

55-
/// Closes tracked database connections.
67+
/// Closes all tracked database connections that are still open.
68+
///
69+
/// Throws a [StateError] if this tracker has already been disposed.
5670
void closeExisting() {
57-
_db.execute('BEGIN;');
71+
_checkIfDisposed();
5872

73+
_db.execute('BEGIN;');
5974
try {
6075
final results =
6176
_db.select('SELECT database_pointer FROM open_connections');
@@ -70,4 +85,30 @@ CREATE TABLE IF NOT EXISTS open_connections(
7085
_db.execute('COMMIT;');
7186
}
7287
}
88+
89+
/// Releases all open database handles managed by this tracker and
90+
/// closes the internal in-memory database.
91+
///
92+
/// After calling this method, this instance should no longer be used.
93+
/// Any further calls to public methods will throw a [StateError].
94+
void dispose() {
95+
if (_isDisposed) {
96+
return;
97+
}
98+
99+
// Safely close any tracked connections
100+
closeExisting();
101+
102+
_db.dispose();
103+
_isDisposed = true;
104+
}
105+
106+
/// Checks if this tracker has been disposed and throws a [StateError] if so.
107+
void _checkIfDisposed() {
108+
if (_isDisposed) {
109+
throw StateError(
110+
'DatabaseTracker has already been disposed and can no longer be used.',
111+
);
112+
}
113+
}
73114
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import 'package:test/test.dart';
2+
import 'package:sqlite3/sqlite3.dart';
3+
import 'package:drift/src/sqlite3/database_tracker.dart';
4+
5+
void main() {
6+
late DatabaseTracker tracker;
7+
8+
setUp(() {
9+
tracker = DatabaseTracker();
10+
});
11+
12+
tearDown(() {
13+
tracker.dispose();
14+
});
15+
16+
test('tracks and closes existing database connections', () {
17+
// Open two in-memory SQLite databases.
18+
final db1 = sqlite3.openInMemory();
19+
final db2 = sqlite3.openInMemory();
20+
21+
// Register each database with the tracker.
22+
tracker.markOpened('db1_path', db1);
23+
tracker.markOpened('db2_path', db2);
24+
25+
// Use the tracker to close all tracked connections.
26+
tracker.closeExisting();
27+
28+
// After closing, further queries should throw an error.
29+
expect(
30+
() => db1.execute('INSERT INTO test1 (id) VALUES (1)'),
31+
throwsA(anything),
32+
);
33+
expect(
34+
() => db2.execute('INSERT INTO test2 (id) VALUES (2)'),
35+
throwsA(anything),
36+
);
37+
});
38+
39+
test('throws StateError after disposal', () {
40+
// Dispose immediately
41+
tracker.dispose();
42+
43+
// Any usage of the tracker after dispose should throw a StateError.
44+
expect(
45+
() => tracker.markOpened('test', sqlite3.openInMemory()),
46+
throwsStateError,
47+
);
48+
expect(() => tracker.closeExisting(), throwsStateError);
49+
});
50+
51+
test('multiple calls to dispose are safe', () {
52+
// Dispose can be called multiple times without throwing errors.
53+
expect(() => tracker.dispose(), returnsNormally);
54+
expect(() => tracker.dispose(), returnsNormally);
55+
});
56+
57+
test('isDisposed reflects tracker state', () {
58+
expect(tracker.isDisposed, isFalse);
59+
60+
tracker.dispose();
61+
expect(tracker.isDisposed, isTrue);
62+
});
63+
}

0 commit comments

Comments
 (0)