Skip to content

Prototype for impl with sqlite3_web package #27

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

Closed
Closed
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
6 changes: 0 additions & 6 deletions lib/drift.dart

This file was deleted.

183 changes: 183 additions & 0 deletions lib/src/web/database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import 'dart:async';
import 'dart:js_interop';

import 'package:sqlite3/common.dart';
import 'package:sqlite3_web/sqlite3_web.dart';
import 'package:sqlite_async/mutex.dart';
import 'package:sqlite_async/src/common/sqlite_database.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'package:sqlite_async/src/sqlite_queries.dart';
import 'package:sqlite_async/src/update_notification.dart';
import 'protocol.dart';

class WebDatabase
with SqliteQueries, SqliteDatabaseMixin
implements SqliteDatabase {
final Database _database;
final Mutex? _mutex;

@override
bool closed = false;

WebDatabase(this._database, this._mutex);

@override
Future<void> close() async {
await _database.dispose();
closed = true;
}

@override
Future<bool> getAutoCommit() async {
final response = await _database.customRequest(
CustomDatabaseMessage(CustomDatabaseMessageKind.getAutoCommit));
return (response as JSBoolean?)?.toDart ?? false;
}

@override
Future<void> initialize() {
return Future.value();
}

@override
Future<void> get isInitialized => initialize();

@override
Never isolateConnectionFactory() {
throw UnimplementedError();
}

@override
int get maxReaders => throw UnimplementedError();

@override
Never get openFactory => throw UnimplementedError();

@override
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
{Duration? lockTimeout, String? debugContext}) async {
if (_mutex case var mutex?) {
return await mutex.lock(() async {
final context = _SharedContext(this);
try {
return await callback(context);
} finally {
context.markClosed();
}
});
} else {
// No custom mutex, coordinate locks through shared worker.
await _database.customRequest(
CustomDatabaseMessage(CustomDatabaseMessageKind.requestSharedLock));

try {
return await callback(_SharedContext(this));
} finally {
await _database.customRequest(
CustomDatabaseMessage(CustomDatabaseMessageKind.releaseLock));
}
}
}

@override
Stream<UpdateNotification> get updates =>
_database.updates.map((event) => UpdateNotification({event.tableName}));

@override
// todo: Why do we have to expose both a stream and a controller?
StreamController<UpdateNotification> get updatesController =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: It should not be necessary to override this. This was added to SqliteDatabaseMixin since we used it for both web and native. But this could be moved from SqliteDatabaseMixin to reside solely in the native implementation.

throw UnimplementedError();

@override
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,
{Duration? lockTimeout, String? debugContext}) async {
if (_mutex case var mutex?) {
return await mutex.lock(() async {
final context = _ExlusiveContext(this);
try {
return await callback(context);
} finally {
context.markClosed();
}
});
} else {
// No custom mutex, coordinate locks through shared worker.
await _database.customRequest(CustomDatabaseMessage(
CustomDatabaseMessageKind.requestExclusiveLock));
final context = _ExlusiveContext(this);

try {
return await callback(context);
} finally {
context.markClosed();
await _database.customRequest(
CustomDatabaseMessage(CustomDatabaseMessageKind.releaseLock));
}
}
}
}

class _SharedContext implements SqliteReadContext {
final WebDatabase _database;
bool _contextClosed = false;

_SharedContext(this._database);

@override
bool get closed => _contextClosed || _database.closed;

@override
Future<T> computeWithDatabase<T>(
Future<T> Function(CommonDatabase db) compute) {
// Can't be implemented: The database may live on another worker.
throw UnimplementedError();
}

@override
Future<Row> get(String sql, [List<Object?> parameters = const []]) async {
final results = await getAll(sql, parameters);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Operations in these contexts should throw instances of SqliteException if it's a corresponding exception. This is used in places like here. This was previously implemented here.

return results.single;
}

@override
Future<ResultSet> getAll(String sql,
[List<Object?> parameters = const []]) async {
return await _database._database.select(sql, parameters);
}

@override
Future<bool> getAutoCommit() async {
return _database.getAutoCommit();
}

@override
Future<Row?> getOptional(String sql,
[List<Object?> parameters = const []]) async {
final results = await getAll(sql, parameters);
return results.singleOrNull;
}

void markClosed() {
_contextClosed = true;
}
}

class _ExlusiveContext extends _SharedContext implements SqliteWriteContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This contains a typo :)

_ExlusiveContext(super.database);

@override
Future<ResultSet> execute(String sql,
[List<Object?> parameters = const []]) async {
return await _database._database.select(sql, parameters);
}

@override
Future<void> executeBatch(
String sql, List<List<Object?>> parameterSets) async {
for (final set in parameterSets) {
// use execute instead of select to avoid transferring rows from the
// worker to this context.
await _database._database.execute(sql, set);
}
}
}
Loading