From 62df8687a07457af05f9b6c648b0eeceb19bbe01 Mon Sep 17 00:00:00 2001 From: suojae Date: Mon, 20 Jan 2025 04:53:47 +0900 Subject: [PATCH] Address CI issues in DatabaseTracker --- drift/lib/src/sqlite3/database_tracker.dart | 55 ++++++++++++-- .../test/database/database_tracker_test.dart | 72 +++++++++++++++++++ 2 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 drift/test/database/database_tracker_test.dart diff --git a/drift/lib/src/sqlite3/database_tracker.dart b/drift/lib/src/sqlite3/database_tracker.dart index 1cb304517..d0fcdb709 100644 --- a/drift/lib/src/sqlite3/database_tracker.dart +++ b/drift/lib/src/sqlite3/database_tracker.dart @@ -24,6 +24,12 @@ final DatabaseTracker tracker = DatabaseTracker(); class DatabaseTracker { final Database _db; + /// Whether this [DatabaseTracker] has been disposed. + bool _isDisposed = false; + + /// Public getter to check if this tracker has been disposed. + bool get isDisposed => _isDisposed; + /// Creates a new tracker with necessary tables. DatabaseTracker() : _db = sqlite3.open( @@ -38,24 +44,36 @@ CREATE TABLE IF NOT EXISTS open_connections( '''); } - /// Tracks the [openedDb]. The [path] argument can be used to track the path - /// of that database, if it's bound to a file. + /// Tracks the [openedDb]. The [path] argument can be used to track + /// the path of that database, if it's bound to a file. + /// + /// Throws a [StateError] if this tracker has already been disposed. void markOpened(String path, Database openedDb) { + _checkIfDisposed(); + final stmt = _db.prepare('INSERT INTO open_connections VALUES (?, ?)'); stmt.execute([openedDb.handle.address, path]); stmt.dispose(); } /// Marks the database [db] as closed. + /// + /// Throws a [StateError] if this tracker has already been disposed. void markClosed(Database db) { + _checkIfDisposed(); + final ptr = db.handle.address; _db.execute('DELETE FROM open_connections WHERE database_pointer = $ptr'); } - /// Closes tracked database connections. + /// Closes all tracked database connections that are still open. + /// + /// This operation is wrapped in a transaction (`BEGIN` ... `COMMIT`). + /// Throws a [StateError] if this tracker has already been disposed. void closeExisting() { - _db.execute('BEGIN;'); + _checkIfDisposed(); + _db.execute('BEGIN;'); try { final results = _db.select('SELECT database_pointer FROM open_connections'); @@ -70,4 +88,33 @@ CREATE TABLE IF NOT EXISTS open_connections( _db.execute('COMMIT;'); } } + + /// Releases all open database handles managed by this tracker and + /// closes the internal in-memory database. + /// + /// After calling this method, this instance should no longer be used. + /// Any further calls to public methods will throw a [StateError]. + void dispose() { + if (_isDisposed) { + return; + } + + // Safely close any tracked connections + closeExisting(); + + // Dispose of the in-memory tracker database + _db.dispose(); + + // Mark this tracker as disposed + _isDisposed = true; + } + + /// Checks if this tracker has been disposed and throws a [StateError] if so. + void _checkIfDisposed() { + if (_isDisposed) { + throw StateError( + 'DatabaseTracker has already been disposed and can no longer be used.', + ); + } + } } diff --git a/drift/test/database/database_tracker_test.dart b/drift/test/database/database_tracker_test.dart new file mode 100644 index 000000000..7ba61878d --- /dev/null +++ b/drift/test/database/database_tracker_test.dart @@ -0,0 +1,72 @@ +import 'package:test/test.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:drift/src/sqlite3/database_tracker.dart'; + +void main() { + group('DatabaseTracker', () { + late DatabaseTracker tracker; + + setUp(() { + // Create a fresh DatabaseTracker instance before each test. + tracker = DatabaseTracker(); + }); + + tearDown(() { + // Clean up resources by disposing of the tracker. + // Multiple dispose calls should be safe. + tracker.dispose(); + }); + + test('tracks and closes existing database connections', () { + // Open two in-memory SQLite databases. + final db1 = sqlite3.openInMemory(); + final db2 = sqlite3.openInMemory(); + + // Register each database with the tracker. + tracker.markOpened('db1_path', db1); + tracker.markOpened('db2_path', db2); + + // Optionally perform some queries to confirm the databases are working. + db1.execute('CREATE TABLE test1 (id INTEGER NOT NULL PRIMARY KEY)'); + db2.execute('CREATE TABLE test2 (id INTEGER NOT NULL PRIMARY KEY)'); + + // Use the tracker to close all tracked connections. + tracker.closeExisting(); + + // After closing, further queries should throw an error. + expect( + () => db1.execute('INSERT INTO test1 (id) VALUES (1)'), + throwsA(anything), + ); + expect( + () => db2.execute('INSERT INTO test2 (id) VALUES (2)'), + throwsA(anything), + ); + }); + + test('throws StateError after disposal', () { + // Dispose immediately + tracker.dispose(); + + // Any usage of the tracker after dispose should throw a StateError. + expect( + () => tracker.markOpened('test', sqlite3.openInMemory()), + throwsStateError, + ); + expect(() => tracker.closeExisting(), throwsStateError); + }); + + test('multiple calls to dispose are safe', () { + // Dispose can be called multiple times without throwing errors. + expect(() => tracker.dispose(), returnsNormally); + expect(() => tracker.dispose(), returnsNormally); + }); + + test('isDisposed reflects tracker state', () { + expect(tracker.isDisposed, isFalse); + + tracker.dispose(); + expect(tracker.isDisposed, isTrue); + }); + }); +}