Skip to content

Commit

Permalink
Address CI issues in DatabaseTracker
Browse files Browse the repository at this point in the history
  • Loading branch information
suojae committed Jan 19, 2025
1 parent 3345223 commit eb224c4
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 6 deletions.
58 changes: 52 additions & 6 deletions drift/lib/src/sqlite3/database_tracker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -38,27 +44,38 @@ 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');
final results = _db.select('SELECT database_pointer FROM open_connections');

for (final row in results) {
final ptr = Pointer.fromAddress(row.columnAt(0) as int);
Expand All @@ -70,4 +87,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.',
);
}
}
}
72 changes: 72 additions & 0 deletions drift/test/database/database_tracker_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
});
}

0 comments on commit eb224c4

Please sign in to comment.