Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address CI issues in DatabaseTracker #3423

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 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,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');
Expand All @@ -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.',
);
}
}
}
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);
});
});
}
Loading