Skip to content

Commit 17545cb

Browse files
committed
feat(database_tracker): add explicit dispose method and corresponding tests
1 parent 91106e9 commit 17545cb

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

drift/lib/src/sqlite3/database_tracker.dart

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ final DatabaseTracker tracker = DatabaseTracker();
2424
class DatabaseTracker {
2525
final Database _db;
2626

27+
/// Whether this [DatabaseTracker] has been disposed.
28+
bool _isDisposed = false;
29+
2730
/// Creates a new tracker with necessary tables.
2831
DatabaseTracker()
2932
: _db = sqlite3.open(
@@ -38,24 +41,36 @@ CREATE TABLE IF NOT EXISTS open_connections(
3841
''');
3942
}
4043

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.
44+
/// Tracks the [openedDb]. The [path] argument can be used to track
45+
/// the path of that database, if it's bound to a file.
46+
///
47+
/// Throws a [StateError] if this tracker has already been disposed.
4348
void markOpened(String path, Database openedDb) {
49+
_checkIfDisposed();
50+
4451
final stmt = _db.prepare('INSERT INTO open_connections VALUES (?, ?)');
4552
stmt.execute([openedDb.handle.address, path]);
4653
stmt.dispose();
4754
}
4855

4956
/// Marks the database [db] as closed.
57+
///
58+
/// Throws a [StateError] if this tracker has already been disposed.
5059
void markClosed(Database db) {
60+
_checkIfDisposed();
61+
5162
final ptr = db.handle.address;
5263
_db.execute('DELETE FROM open_connections WHERE database_pointer = $ptr');
5364
}
5465

55-
/// Closes tracked database connections.
66+
/// Closes all tracked database connections that are still open.
67+
///
68+
/// This operation is wrapped in a transaction (`BEGIN` ... `COMMIT`).
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,33 @@ 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+
// Dispose of the in-memory tracker database
103+
_db.dispose();
104+
105+
// Mark this tracker as disposed
106+
_isDisposed = true;
107+
}
108+
109+
/// Checks if this tracker has been disposed and throws a [StateError] if so.
110+
void _checkIfDisposed() {
111+
if (_isDisposed) {
112+
throw StateError(
113+
'DatabaseTracker has already been disposed and can no longer be used.',
114+
);
115+
}
116+
}
73117
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
group('DatabaseTracker', () {
7+
late DatabaseTracker tracker;
8+
9+
setUp(() {
10+
// Create a fresh DatabaseTracker instance before each test.
11+
tracker = DatabaseTracker();
12+
});
13+
14+
tearDown(() {
15+
// Clean up resources by disposing of the tracker.
16+
// Multiple dispose calls should be safe.
17+
tracker.dispose();
18+
});
19+
20+
test('tracks and closes existing database connections', () {
21+
// Open two in-memory SQLite databases.
22+
final db1 = sqlite3.openInMemory();
23+
final db2 = sqlite3.openInMemory();
24+
25+
// Register each database with the tracker.
26+
tracker.markOpened('db1_path', db1);
27+
tracker.markOpened('db2_path', db2);
28+
29+
// Optionally perform some queries to confirm the databases are working.
30+
db1.execute('CREATE TABLE test1 (id INTEGER NOT NULL PRIMARY KEY)');
31+
db2.execute('CREATE TABLE test2 (id INTEGER NOT NULL PRIMARY KEY)');
32+
33+
// Use the tracker to close all tracked connections.
34+
tracker.closeExisting();
35+
36+
// After closing, further queries should throw an error.
37+
expect(
38+
() => db1.execute('INSERT INTO test1 (id) VALUES (1)'),
39+
throwsA(anything),
40+
);
41+
expect(
42+
() => db2.execute('INSERT INTO test2 (id) VALUES (2)'),
43+
throwsA(anything),
44+
);
45+
});
46+
47+
test('throws StateError after disposal', () {
48+
// Dispose immediately
49+
tracker.dispose();
50+
51+
// Any usage of the tracker after dispose should throw a StateError.
52+
expect(
53+
() => tracker.markOpened('test', sqlite3.openInMemory()),
54+
throwsStateError,
55+
);
56+
expect(() => tracker.closeExisting(), throwsStateError);
57+
});
58+
59+
test('multiple calls to dispose are safe', () {
60+
// Dispose can be called multiple times without throwing errors.
61+
expect(() => tracker.dispose(), returnsNormally);
62+
expect(() => tracker.dispose(), returnsNormally);
63+
});
64+
});
65+
}

0 commit comments

Comments
 (0)