Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
8214ab1
wip
stevensJourney Dec 4, 2023
7f7a05e
common database types
stevensJourney Dec 6, 2023
9658a6b
added abstractions
stevensJourney Dec 6, 2023
13c6d94
remove test log
stevensJourney Jan 8, 2024
8ae36c8
added web abstractions
stevensJourney Jan 10, 2024
55252e4
abstract factories. Fix tests
stevensJourney Jan 11, 2024
e54f9bb
cleanup isolates
stevensJourney Jan 11, 2024
e011bd0
loading WASM implementation
stevensJourney Jan 11, 2024
f433b23
wip: added synchronous db implementation
stevensJourney Jan 12, 2024
58453ee
bug fixes
stevensJourney Jan 15, 2024
40fbd1d
sqlite version note
stevensJourney Jan 16, 2024
c7f22f8
added async DB operations with Drift
stevensJourney Jan 16, 2024
7e406a3
wip: drift
stevensJourney Jan 25, 2024
c6f678a
allow multiple web connections (leveraging web worker)
stevensJourney Jan 25, 2024
5246658
wip
stevensJourney Jan 25, 2024
14a3fc7
wip isolate connection factory
stevensJourney Jan 25, 2024
b56e64a
wip: organize different implementations
stevensJourney Jan 25, 2024
4327244
cleanup tests
stevensJourney Jan 25, 2024
8e7b1ff
ignore vscode
stevensJourney Jan 25, 2024
b033794
cleanup mutexes
stevensJourney Jan 25, 2024
30b6b6c
standard Drift is fine for this lib. Changes are on compiled worker.
stevensJourney Jan 26, 2024
6fc0e10
update sqlite dependency range for test and native compatibility
stevensJourney Jan 26, 2024
c2371fc
test sdk 3.2.0
stevensJourney Jan 26, 2024
e8b0bf8
lint
stevensJourney Jan 26, 2024
699f3a3
js versions
stevensJourney Jan 26, 2024
b841a47
update from main
stevensJourney Jan 29, 2024
c2d3e72
update from main
stevensJourney Jan 29, 2024
1283573
added comments
stevensJourney Jan 29, 2024
852633b
added readme note
stevensJourney Jan 29, 2024
eff0a10
use mutexes more in abstractions.
stevensJourney Jan 29, 2024
9d3786a
wip: abstract tests. Add Web server for WASM files
stevensJourney Jan 30, 2024
96d67b9
linting fixes
stevensJourney Jan 30, 2024
2892d21
fix tests
stevensJourney Jan 31, 2024
1243a5d
split native and web tests. Add zone gaurds to web connections
stevensJourney Feb 1, 2024
9f16eff
run web tests in CI
stevensJourney Feb 1, 2024
23e211a
test: only compile assets once
stevensJourney Feb 1, 2024
e591cdc
compile worker only once
stevensJourney Feb 1, 2024
c001b3b
fix compile cmd typo
stevensJourney Feb 1, 2024
6ffb129
cleanup tests
stevensJourney Feb 1, 2024
02c2d9f
improve web locks and transactions according to test spec
stevensJourney Feb 1, 2024
1145db3
Convert Drift SQL exceptions to SQLite exceptions
stevensJourney Feb 2, 2024
70c2324
Allow autocommit on web database connection
stevensJourney Feb 5, 2024
3e4bdd4
remove duplicate test
stevensJourney Feb 5, 2024
5307003
use forked version of Drift
stevensJourney Feb 6, 2024
fc4b55d
enable shared watch tests with custom Drift worker
stevensJourney Feb 6, 2024
f67d802
migrate from to naming
stevensJourney Feb 6, 2024
f946e1a
Use standard Drift package for now. Forked Drift is not published
stevensJourney Feb 7, 2024
cd3386b
less strict package version
stevensJourney Feb 8, 2024
fdd09c3
code cleanup
stevensJourney Feb 8, 2024
ba1622c
improved auto commit check to only conditionally run
stevensJourney Feb 13, 2024
af05d85
remove some abstracted classes - replace with factory generators
stevensJourney Feb 13, 2024
a35fa43
fix getOptional method to throw expections correctly
stevensJourney Feb 13, 2024
ab00e64
cleanup exports
stevensJourney Feb 13, 2024
0263cee
allow creating Mutex class from factory
stevensJourney Feb 13, 2024
3960604
neaten up comments
stevensJourney Feb 13, 2024
9adb4c6
temporarily bump exit-code-threshold to 10. While waiting for depend…
stevensJourney Feb 14, 2024
2275266
bump package version
stevensJourney Feb 14, 2024
99d6289
added changelog entry
stevensJourney Feb 14, 2024
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
11 changes: 8 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Check publish score
run: |
dart pub global activate pana
dart pub global run pana --no-warning --exit-code-threshold 0
dart pub global run pana --no-warning --exit-code-threshold 10

test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -55,9 +55,14 @@ jobs:
run: dart pub get

- name: Install SQLite
run: ./scripts/install_sqlite.sh ${{ matrix.sqlite_version }} ${{ matrix.sqlite_url }}
run: |
./scripts/install_sqlite.sh ${{ matrix.sqlite_version }} ${{ matrix.sqlite_url }}
mkdir -p assets && curl -LJ https://github.com/simolus3/sqlite3.dart/releases/download/sqlite3-2.3.0/sqlite3.wasm -o assets/sqlite3.wasm

- name: Compile WebWorker
run: dart compile js -o assets/db_worker.js -O0 lib/src/web/worker/drift_worker.dart

- name: Run Tests
run: |
export LD_LIBRARY_PATH=./sqlite-autoconf-${{ matrix.sqlite_version }}/.libs
dart test
dart test -p vm,chrome
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

# Test assets
assets

.idea
.vscode
*.db
*.db-*
test-db
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.7.0-alpha.1

- Added initial support for web platform.

## 0.6.0

- Allow catching errors and continuing the transaction. This is technically a breaking change, although it should not be an issue in most cases.
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,29 @@ void main() async {

await db.close();
}
```
```

# Web

Web support is provided by the [Drift](https://github.com/powersync-ja/drift/pull/1) library. Detailed instructions for compatibility and setup are listed in the link.

Web support requires Sqlite3 WASM and Drift worker Javascript files to be accessible via configurable URIs.

Default URIs are shown in the example below. URIs only need to be specified if they differ from default values.

Watched queries and table change notifications are only supported when using a custom Drift worker which is compiled by linking
https://github.com/powersync-ja/drift/pull/1.

Setup

``` Dart
import 'package:sqlite_async/sqlite_async.dart';

final db = SqliteDatabase(
path: 'test.db',
options: SqliteOptions(
webSqliteOptions: WebSqliteOptions(
wasmUri: 'sqlite3.wasm', workerUri: 'db_worker.js')));

```

11 changes: 6 additions & 5 deletions example/custom_functions_example.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:sqlite3/common.dart';
import 'package:sqlite_async/sqlite_async.dart';
import 'package:sqlite3/sqlite3.dart' as sqlite;

/// Since the functions need to be created on every SQLite connection,
/// we do this in a SqliteOpenFactory.
class TestOpenFactory extends DefaultSqliteOpenFactory {
TestOpenFactory({required super.path, super.sqliteOptions});

@override
sqlite.Database open(SqliteOpenOptions options) {
final db = super.open(options);
FutureOr<CommonDatabase> open(SqliteOpenOptions options) async {
final db = await super.open(options);

db.createFunction(
functionName: 'sleep',
argumentCount: const sqlite.AllowedArgumentCount(1),
argumentCount: const AllowedArgumentCount(1),
function: (args) {
final millis = args[0] as int;
sleep(Duration(milliseconds: millis));
Expand All @@ -25,7 +26,7 @@ class TestOpenFactory extends DefaultSqliteOpenFactory {

db.createFunction(
functionName: 'isolate_name',
argumentCount: const sqlite.AllowedArgumentCount(0),
argumentCount: const AllowedArgumentCount(0),
function: (args) {
return Isolate.current.debugName;
},
Expand Down
5 changes: 3 additions & 2 deletions example/linux_cli_example.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'dart:async';
import 'dart:ffi';

import 'package:sqlite3/common.dart';
import 'package:sqlite_async/sqlite_async.dart';
import 'package:sqlite3/open.dart' as sqlite_open;
import 'package:sqlite3/sqlite3.dart' as sqlite;

const defaultSqlitePath = 'libsqlite3.so.0';

Expand All @@ -16,7 +17,7 @@ class TestOpenFactory extends DefaultSqliteOpenFactory {
this.sqlitePath = defaultSqlitePath});

@override
sqlite.Database open(SqliteOpenOptions options) {
FutureOr<CommonDatabase> open(SqliteOpenOptions options) async {
// For details, see:
// https://pub.dev/packages/sqlite3#manually-providing-sqlite3-libraries
sqlite_open.open.overrideFor(sqlite_open.OperatingSystem.linux, () {
Expand Down
6 changes: 6 additions & 0 deletions lib/drift.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Re-exports [Drift](https://pub.dev/packages/drift) to expose drift without
/// adding it as a direct dependency.
library;

export 'package:drift/wasm.dart';
export 'package:sqlite_async/src/web/worker/worker_utils.dart';
2 changes: 2 additions & 0 deletions lib/sqlite3_common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Exports common Sqlite3 exports which are available on web and ffi environments
export 'package:sqlite3/common.dart';
6 changes: 6 additions & 0 deletions lib/sqlite_async.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
/// See [SqliteDatabase] as a starting point.
library;

export 'src/common/abstract_open_factory.dart';
export 'src/common/connection/sync_sqlite_connection.dart';
export 'src/common/isolate_connection_factory.dart';
export 'src/common/mutex.dart';
export 'src/common/port_channel.dart';
export 'src/common/sqlite_database.dart';
export 'src/isolate_connection_factory.dart';
export 'src/sqlite_connection.dart';
export 'src/sqlite_database.dart';
Expand Down
69 changes: 69 additions & 0 deletions lib/src/common/abstract_open_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:async';
import 'package:meta/meta.dart';

import 'package:sqlite_async/sqlite3_common.dart' as sqlite;
import 'package:sqlite_async/src/sqlite_options.dart';

/// Factory to create new SQLite database connections.
///
/// Since connections are opened in dedicated background isolates, this class
/// must be safe to pass to different isolates.
abstract class SqliteOpenFactory<Database extends sqlite.CommonDatabase> {
String get path;

FutureOr<Database> open(SqliteOpenOptions options);
}

class SqliteOpenOptions {
/// Whether this is the primary write connection for the database.
final bool primaryConnection;

/// Whether this connection is read-only.
final bool readOnly;

const SqliteOpenOptions(
{required this.primaryConnection, required this.readOnly});

sqlite.OpenMode get openMode {
if (primaryConnection) {
return sqlite.OpenMode.readWriteCreate;
} else if (readOnly) {
return sqlite.OpenMode.readOnly;
} else {
return sqlite.OpenMode.readWrite;
}
}
}

/// The default database factory.
///
/// This takes care of opening the database, and running PRAGMA statements
/// to configure the connection.
///
/// Override the [open] method to customize the process.
abstract class AbstractDefaultSqliteOpenFactory<
Database extends sqlite.CommonDatabase>
implements SqliteOpenFactory<Database> {
@override
final String path;
final SqliteOptions sqliteOptions;

const AbstractDefaultSqliteOpenFactory(
{required this.path,
this.sqliteOptions = const SqliteOptions.defaults()});

List<String> pragmaStatements(SqliteOpenOptions options);

@protected
FutureOr<Database> openDB(SqliteOpenOptions options);

@override
FutureOr<Database> open(SqliteOpenOptions options) async {
var db = await openDB(options);

for (var statement in pragmaStatements(options)) {
db.execute(statement);
}
return db;
}
}
116 changes: 116 additions & 0 deletions lib/src/common/connection/sync_sqlite_connection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import 'package:sqlite3/common.dart';
import 'package:sqlite_async/src/common/mutex.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';

/// A simple "synchronous" connection which provides the async SqliteConnection
/// implementation using a synchronous SQLite connection
class SyncSqliteConnection extends SqliteConnection with SqliteQueries {
final CommonDatabase db;
late Mutex mutex;
@override
late final Stream<UpdateNotification> updates;

bool _closed = false;

SyncSqliteConnection(this.db, Mutex m) {
mutex = m.open();
updates = db.updates.map(
(event) {
return UpdateNotification({event.tableName});
},
);
}

@override
Future<T> readLock<T>(Future<T> Function(SqliteReadContext tx) callback,
{Duration? lockTimeout, String? debugContext}) {
return mutex.lock(() => callback(SyncReadContext(db)),
timeout: lockTimeout);
}

@override
Future<T> writeLock<T>(Future<T> Function(SqliteWriteContext tx) callback,
{Duration? lockTimeout, String? debugContext}) {
return mutex.lock(() => callback(SyncWriteContext(db)),
timeout: lockTimeout);
}

@override
Future<void> close() async {
_closed = true;
return db.dispose();
}

@override
bool get closed => _closed;

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

class SyncReadContext implements SqliteReadContext {
CommonDatabase db;

SyncReadContext(this.db);

@override
Future<T> computeWithDatabase<T>(
Future<T> Function(CommonDatabase db) compute) {
return compute(db);
}

@override
Future<Row> get(String sql, [List<Object?> parameters = const []]) async {
return db.select(sql, parameters).first;
}

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

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

@override
bool get closed => false;

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

class SyncWriteContext extends SyncReadContext implements SqliteWriteContext {
SyncWriteContext(super.db);

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

@override
Future<void> executeBatch(
String sql, List<List<Object?>> parameterSets) async {
return computeWithDatabase((db) async {
final statement = db.prepare(sql, checkNoTail: true);
try {
for (var parameters in parameterSets) {
statement.execute(parameters);
}
} finally {
statement.dispose();
}
});
}
}
47 changes: 47 additions & 0 deletions lib/src/common/isolate_connection_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:async';
import 'package:sqlite_async/sqlite3_common.dart' as sqlite;
import 'package:sqlite_async/src/common/mutex.dart';
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
import 'package:sqlite_async/src/impl/isolate_connection_factory_impl.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';
import 'port_channel.dart';

mixin IsolateOpenFactoryMixin<Database extends sqlite.CommonDatabase> {
AbstractDefaultSqliteOpenFactory<Database> get openFactory;

/// Opens a synchronous sqlite.Database directly in the current isolate.
///
/// This gives direct access to the database, but:
/// 1. No app-level locking is performed automatically. Transactions may fail
/// with SQLITE_BUSY if another isolate is using the database at the same time.
/// 2. Other connections are not notified of any updates to tables made within
/// this connection.
FutureOr<Database> openRawDatabase({bool readOnly = false}) async {
return openFactory
.open(SqliteOpenOptions(primaryConnection: false, readOnly: readOnly));
}
}

/// A connection factory that can be passed to different isolates.
abstract class IsolateConnectionFactory<Database extends sqlite.CommonDatabase>
with IsolateOpenFactoryMixin {
Mutex get mutex;

SerializedPortClient get upstreamPort;

factory IsolateConnectionFactory(
{required openFactory,
required mutex,
SerializedPortClient? upstreamPort}) {
return IsolateConnectionFactoryImpl(
openFactory: openFactory,
mutex: mutex,
upstreamPort: upstreamPort as SerializedPortClient)
as IsolateConnectionFactory<Database>;
}

/// Open a new SqliteConnection.
///
/// This opens a single connection in a background execution isolate.
SqliteConnection open({String? debugName, bool readOnly = false});
}
Loading