Skip to content
Merged
Show file tree
Hide file tree
Changes from 49 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
9 changes: 7 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
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
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
8 changes: 8 additions & 0 deletions lib/definitions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export 'package:sqlite_async/src/update_notification.dart';
export 'package:sqlite_async/src/sqlite_connection.dart';
export 'package:sqlite_async/src/sqlite_queries.dart';
export 'package:sqlite_async/src/sqlite_open_factory.dart';
export 'package:sqlite_async/src/sqlite_options.dart';
export 'package:sqlite_async/src/common/abstract_isolate_connection_factory.dart';
export 'package:sqlite_async/src/common/abstract_open_factory.dart';
export 'package:sqlite_async/src/common/abstract_sqlite_database.dart';
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';
3 changes: 2 additions & 1 deletion lib/sqlite_async.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export 'src/sqlite_connection.dart';
export 'src/sqlite_database.dart';
export 'src/sqlite_migrations.dart';
export 'src/sqlite_open_factory.dart';
export 'src/sqlite_options.dart';
export 'src/sqlite_queries.dart';
export 'src/update_notification.dart';
export 'src/utils.dart';
export 'definitions.dart';
export 'src/common/connection/sync_sqlite_connection.dart';
34 changes: 34 additions & 0 deletions lib/src/common/abstract_isolate_connection_factory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'dart:async';
import 'package:sqlite_async/mutex.dart';
import 'package:sqlite_async/sqlite3_common.dart' as sqlite;
import 'package:sqlite_async/src/sqlite_connection.dart';

import 'abstract_open_factory.dart';
import 'port_channel.dart';

/// A connection factory that can be passed to different isolates.
abstract class AbstractIsolateConnectionFactory<
Database extends sqlite.CommonDatabase> {
AbstractDefaultSqliteOpenFactory<Database> get openFactory;

AbstractMutex get mutex;

SerializedPortClient get upstreamPort;

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

/// 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));
}
}
26 changes: 26 additions & 0 deletions lib/src/common/abstract_mutex.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
abstract class AbstractMutex {
/// timeout is a timeout for acquiring the lock, not for the callback
Future<T> lock<T>(Future<T> Function() callback, {Duration? timeout});

/// Use [open] to get a [AbstractMutex] instance.
/// This is mainly used for shared mutexes
AbstractMutex open() {
return this;
}

/// Release resources used by the Mutex.
///
/// Subsequent calls to [lock] may fail, or may never call the callback.
Future<void> close();
}

class LockError extends Error {
final String message;

LockError(this.message);

@override
String toString() {
return 'LockError: $message';
}
}
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;
}
}
48 changes: 48 additions & 0 deletions lib/src/common/abstract_sqlite_database.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:async';

import 'package:sqlite_async/src/common/abstract_isolate_connection_factory.dart';
import 'package:sqlite_async/src/common/abstract_open_factory.dart';
import 'package:sqlite_async/src/sqlite_queries.dart';
import 'package:sqlite_async/src/update_notification.dart';
import 'package:sqlite_async/src/sqlite_connection.dart';

/// A SQLite database instance.
///
/// Use one instance per database file. If multiple instances are used, update
/// notifications may not trigger, and calls may fail with "SQLITE_BUSY" errors.
abstract class AbstractSqliteDatabase extends SqliteConnection
with SqliteQueries {
/// The maximum number of concurrent read transactions if not explicitly specified.
static const int defaultMaxReaders = 5;

/// Maximum number of concurrent read transactions.
int get maxReaders;

/// Factory that opens a raw database connection in each isolate.
///
/// This must be safe to pass to different isolates.
///
/// Use a custom class for this to customize the open process.
AbstractDefaultSqliteOpenFactory get openFactory;

/// Use this stream to subscribe to notifications of updates to tables.
@override
Stream<UpdateNotification> get updates;

final StreamController<UpdateNotification> updatesController =
StreamController.broadcast();

Future<void> get isInitialized;

/// Wait for initialization to complete.
///
/// While initializing is automatic, this helps to catch and report initialization errors.
Future<void> initialize() async {
await isInitialized;
}

/// A connection factory that can be passed to different isolates.
///
/// Use this to access the database in background isolates.
AbstractIsolateConnectionFactory isolateConnectionFactory();
}
Loading