From 3d3404054f9e815e6686fe86af5f5ec3ba413edf Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 13 Aug 2024 16:40:29 +0200 Subject: [PATCH 1/9] Added refreshSchema() to SqliteConnection interface. --- packages/sqlite_async/CHANGELOG.md | 6 +++++- .../src/common/connection/sync_sqlite_connection.dart | 5 +++++ .../sqlite_async/lib/src/impl/stub_sqlite_database.dart | 5 +++++ .../lib/src/native/database/connection_pool.dart | 9 +++++++++ .../native/database/native_sqlite_connection_impl.dart | 5 +++++ .../lib/src/native/database/native_sqlite_database.dart | 5 +++++ packages/sqlite_async/lib/src/sqlite_connection.dart | 4 ++++ packages/sqlite_async/lib/src/web/database.dart | 5 +++++ .../lib/src/web/database/web_sqlite_database.dart | 5 +++++ 9 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index cd48d89..65580f3 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,6 +1,10 @@ +## 0.8.2 + +- Added `refreshSchema()` to `SqliteConnection` and its implementations, allowing queries and watch calls to work against update schemas. + ## 0.8.1 - - Added Navigator locks for web `Mutex`s. +- Added Navigator locks for web `Mutex`s. ## 0.8.0 diff --git a/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart b/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart index f29520f..623d2a1 100644 --- a/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart +++ b/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart @@ -50,6 +50,11 @@ class SyncSqliteConnection extends SqliteConnection with SqliteQueries { Future getAutoCommit() async { return db.autocommit; } + + @override + Future refreshSchema() async { + db.execute("PRAGMA table_info('sqlite_master')"); + } } class SyncReadContext implements SqliteReadContext { diff --git a/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart b/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart index 29db641..d09c37a 100644 --- a/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart @@ -64,4 +64,9 @@ class SqliteDatabaseImpl Future getAutoCommit() { throw UnimplementedError(); } + + @override + Future refreshSchema() { + throw UnimplementedError(); + } } diff --git a/packages/sqlite_async/lib/src/native/database/connection_pool.dart b/packages/sqlite_async/lib/src/native/database/connection_pool.dart index 56d9c12..c0c7d9b 100644 --- a/packages/sqlite_async/lib/src/native/database/connection_pool.dart +++ b/packages/sqlite_async/lib/src/native/database/connection_pool.dart @@ -221,6 +221,15 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { // read-only connections first. await _writeConnection?.close(); } + + @override + Future refreshSchema() async { + final toRefresh = _allReadConnections.toList(); + + for (var connection in toRefresh) { + await connection.refreshSchema(); + } + } } typedef ReadCallback = Future Function(SqliteReadContext tx); diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart index b7ef76b..fa50a47 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart @@ -158,6 +158,11 @@ class SqliteConnectionImpl }); }, timeout: lockTimeout); } + + @override + Future refreshSchema() async { + await get("PRAGMA table_info('sqlite_master')"); + } } int _nextCtxId = 1; diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart index 998fb79..2759b66 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart @@ -166,4 +166,9 @@ class SqliteDatabaseImpl readOnly: false, openFactory: openFactory); } + + @override + Future refreshSchema() async { + await _pool.refreshSchema(); + } } diff --git a/packages/sqlite_async/lib/src/sqlite_connection.dart b/packages/sqlite_async/lib/src/sqlite_connection.dart index f92d318..a69f307 100644 --- a/packages/sqlite_async/lib/src/sqlite_connection.dart +++ b/packages/sqlite_async/lib/src/sqlite_connection.dart @@ -128,6 +128,10 @@ abstract class SqliteConnection extends SqliteWriteContext { Future writeLock(Future Function(SqliteWriteContext tx) callback, {Duration? lockTimeout, String? debugContext}); + /// Ensures that all connections are aware of the latest schema changes applied (if any). + /// Queries and watch calls can potentially use outdated schema information after a schema update. + Future refreshSchema(); + Future close(); /// Returns true if the connection is closed diff --git a/packages/sqlite_async/lib/src/web/database.dart b/packages/sqlite_async/lib/src/web/database.dart index d7b78bf..8e05477 100644 --- a/packages/sqlite_async/lib/src/web/database.dart +++ b/packages/sqlite_async/lib/src/web/database.dart @@ -129,6 +129,11 @@ class WebDatabase } } } + + @override + Future refreshSchema() async { + _database.execute("PRAGMA table_info('sqlite_master')"); + } } class _SharedContext implements SqliteReadContext { diff --git a/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart b/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart index 522b48e..c17e873 100644 --- a/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart @@ -139,4 +139,9 @@ class SqliteDatabaseImpl await isInitialized; return _connection.getAutoCommit(); } + + @override + Future refreshSchema() async { + await _connection.refreshSchema(); + } } From d278237b6bfa2b0e05d4a12c5b8b1a688b1b28fd Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Wed, 14 Aug 2024 17:37:07 +0200 Subject: [PATCH 2/9] Simplifying refreshSchema implementation by introducing exlusiveLock and refreshSchema in `sqlite_database`. --- packages/drift_sqlite_async/pubspec.yaml | 2 +- .../connection/sync_sqlite_connection.dart | 5 - .../lib/src/common/sqlite_database.dart | 15 +++ .../lib/src/impl/stub_sqlite_database.dart | 5 - .../src/native/database/connection_pool.dart | 72 ++++++++++-- .../native_sqlite_connection_impl.dart | 5 - .../database/native_sqlite_database.dart | 5 +- .../lib/src/sqlite_connection.dart | 4 - .../sqlite_async/lib/src/web/database.dart | 5 - .../src/web/database/web_sqlite_database.dart | 5 - packages/sqlite_async/pubspec.yaml | 2 +- .../sqlite_async/test/native/schema_test.dart | 103 ++++++++++++++++++ 12 files changed, 186 insertions(+), 42 deletions(-) create mode 100644 packages/sqlite_async/test/native/schema_test.dart diff --git a/packages/drift_sqlite_async/pubspec.yaml b/packages/drift_sqlite_async/pubspec.yaml index 842af6e..eda5f50 100644 --- a/packages/drift_sqlite_async/pubspec.yaml +++ b/packages/drift_sqlite_async/pubspec.yaml @@ -15,7 +15,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: drift: ">=2.15.0 <2.19.0" - sqlite_async: ^0.8.1 + sqlite_async: ^0.8.2 dev_dependencies: build_runner: ^2.4.8 drift_dev: ">=2.15.0 <2.19.0" diff --git a/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart b/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart index 623d2a1..f29520f 100644 --- a/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart +++ b/packages/sqlite_async/lib/src/common/connection/sync_sqlite_connection.dart @@ -50,11 +50,6 @@ class SyncSqliteConnection extends SqliteConnection with SqliteQueries { Future getAutoCommit() async { return db.autocommit; } - - @override - Future refreshSchema() async { - db.execute("PRAGMA table_info('sqlite_master')"); - } } class SyncReadContext implements SqliteReadContext { diff --git a/packages/sqlite_async/lib/src/common/sqlite_database.dart b/packages/sqlite_async/lib/src/common/sqlite_database.dart index f8e0be5..02f4580 100644 --- a/packages/sqlite_async/lib/src/common/sqlite_database.dart +++ b/packages/sqlite_async/lib/src/common/sqlite_database.dart @@ -38,6 +38,21 @@ mixin SqliteDatabaseMixin implements SqliteConnection, SqliteQueries { /// /// Use this to access the database in background isolates. IsolateConnectionFactory isolateConnectionFactory(); + + /// TODO Improve on this definition by supporting a writeable context. + Future exclusiveLock( + Future Function(SqliteReadContext ctx) callback, + ) { + return writeLock(callback); + } + + /// Ensures that all connections are aware of the latest schema changes applied (if any). + /// Queries and watch calls can potentially use outdated schema information after a schema update. + Future refreshSchema() { + return exclusiveLock((ctx) async { + return ctx.get("PRAGMA table_info('sqlite_master')"); + }); + } } /// A SQLite database instance. diff --git a/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart b/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart index d09c37a..29db641 100644 --- a/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/impl/stub_sqlite_database.dart @@ -64,9 +64,4 @@ class SqliteDatabaseImpl Future getAutoCommit() { throw UnimplementedError(); } - - @override - Future refreshSchema() { - throw UnimplementedError(); - } } diff --git a/packages/sqlite_async/lib/src/native/database/connection_pool.dart b/packages/sqlite_async/lib/src/native/database/connection_pool.dart index c0c7d9b..9667907 100644 --- a/packages/sqlite_async/lib/src/native/database/connection_pool.dart +++ b/packages/sqlite_async/lib/src/native/database/connection_pool.dart @@ -54,6 +54,69 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { _writeConnection?.updates?.forEach(updatesController.add); } + /// Executes a provided callback function exclusively across all read and + /// write connections in the pool. + /// + /// This function first locks all read and write connections, collecting their + /// contexts. It then executes the provided [callback] function on each of these + /// contexts. After the [callback] completex for each context, the locks are released + /// + /// Example usage: + /// ```dart + /// await runExclusive((ctx) async { + /// // Perform some database operation with the ctx + /// await ctx.execute('PRAGMA schema_version'); + /// }); + /// ``` + /// + /// + exclusiveLock( + Future Function(SqliteReadContext tx) callback, + ) async { + final List> completers = []; + final List> releasers = []; + + for (final read in _allReadConnections) { + final completer = Completer(); + + completers.add(completer); + read.readLock((ctx) async { + completer.complete(ctx); + + final releaser = Completer(); + releasers.add(releaser); + + // Keep this active, close the context when finished + await releaser.future; + }); + } + + final writeCompleter = Completer(); + completers.add(writeCompleter); + _writeConnection?.writeLock((ctx) async { + writeCompleter.complete(ctx); + + final releaser = Completer(); + releasers.add(releaser); + await releaser.future; + }); + + // Get all the connection contexts and execute the callback on each of them + final List contexts = []; + for (final completer in completers) { + contexts.add(await completer.future); + } + + for (final c in contexts) { + await callback(c); + } + + // Release all the releasers + for (final r in releasers) { + r.complete(); + } + } + /// Returns true if the _write_ connection is currently in autocommit mode. @override Future getAutoCommit() async { @@ -221,15 +284,6 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { // read-only connections first. await _writeConnection?.close(); } - - @override - Future refreshSchema() async { - final toRefresh = _allReadConnections.toList(); - - for (var connection in toRefresh) { - await connection.refreshSchema(); - } - } } typedef ReadCallback = Future Function(SqliteReadContext tx); diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart index fa50a47..b7ef76b 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_connection_impl.dart @@ -158,11 +158,6 @@ class SqliteConnectionImpl }); }, timeout: lockTimeout); } - - @override - Future refreshSchema() async { - await get("PRAGMA table_info('sqlite_master')"); - } } int _nextCtxId = 1; diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart index 2759b66..3557859 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart @@ -168,7 +168,8 @@ class SqliteDatabaseImpl } @override - Future refreshSchema() async { - await _pool.refreshSchema(); + Future exclusiveLock( + Future Function(SqliteReadContext ctx) callback) { + return _pool.exclusiveLock(callback); } } diff --git a/packages/sqlite_async/lib/src/sqlite_connection.dart b/packages/sqlite_async/lib/src/sqlite_connection.dart index a69f307..f92d318 100644 --- a/packages/sqlite_async/lib/src/sqlite_connection.dart +++ b/packages/sqlite_async/lib/src/sqlite_connection.dart @@ -128,10 +128,6 @@ abstract class SqliteConnection extends SqliteWriteContext { Future writeLock(Future Function(SqliteWriteContext tx) callback, {Duration? lockTimeout, String? debugContext}); - /// Ensures that all connections are aware of the latest schema changes applied (if any). - /// Queries and watch calls can potentially use outdated schema information after a schema update. - Future refreshSchema(); - Future close(); /// Returns true if the connection is closed diff --git a/packages/sqlite_async/lib/src/web/database.dart b/packages/sqlite_async/lib/src/web/database.dart index 8e05477..d7b78bf 100644 --- a/packages/sqlite_async/lib/src/web/database.dart +++ b/packages/sqlite_async/lib/src/web/database.dart @@ -129,11 +129,6 @@ class WebDatabase } } } - - @override - Future refreshSchema() async { - _database.execute("PRAGMA table_info('sqlite_master')"); - } } class _SharedContext implements SqliteReadContext { diff --git a/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart b/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart index c17e873..522b48e 100644 --- a/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/web/database/web_sqlite_database.dart @@ -139,9 +139,4 @@ class SqliteDatabaseImpl await isInitialized; return _connection.getAutoCommit(); } - - @override - Future refreshSchema() async { - await _connection.refreshSchema(); - } } diff --git a/packages/sqlite_async/pubspec.yaml b/packages/sqlite_async/pubspec.yaml index 58eb5df..e151aa8 100644 --- a/packages/sqlite_async/pubspec.yaml +++ b/packages/sqlite_async/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlite_async description: High-performance asynchronous interface for SQLite on Dart and Flutter. -version: 0.8.1 +version: 0.8.2 repository: https://github.com/powersync-ja/sqlite_async.dart environment: sdk: ">=3.4.0 <4.0.0" diff --git a/packages/sqlite_async/test/native/schema_test.dart b/packages/sqlite_async/test/native/schema_test.dart new file mode 100644 index 0000000..96f4d69 --- /dev/null +++ b/packages/sqlite_async/test/native/schema_test.dart @@ -0,0 +1,103 @@ +@TestOn('!browser') +import 'dart:async'; + +import 'package:sqlite_async/sqlite_async.dart'; +import 'package:sqlite_async/src/utils/shared_utils.dart'; +import 'package:test/test.dart'; + +import '../utils/test_utils_impl.dart'; + +final testUtils = TestUtils(); + +void main() { + group('Schema Tests', () { + late String path; + + setUp(() async { + path = testUtils.dbPath(); + await testUtils.cleanDb(path: path); + }); + + tearDown(() async { + await testUtils.cleanDb(path: path); + }); + + createTables(SqliteDatabase db) async { + await db.writeTransaction((tx) async { + await tx.execute( + 'CREATE TABLE _customers(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'); + await tx.execute( + 'CREATE TABLE _local_customers(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)'); + await tx + .execute('CREATE VIEW customers AS SELECT * FROM _local_customers'); + }); + } + + updateTables(SqliteDatabase db) async { + await db.writeTransaction((tx) async { + await tx.execute('DROP VIEW IF EXISTS customers'); + await tx.execute('CREATE VIEW customers AS SELECT * FROM _customers'); + }); + } + + test('should refresh schema views', () async { + final db = await testUtils.setupDatabase(path: path); + await createTables(db); + + final customerTables = + await getSourceTables(db, "select * from customers"); + expect(customerTables.contains('_local_customers'), true); + await updateTables(db); + + // without this, source tables are outdated + await db.refreshSchema(); + + final updatedCustomerTables = + await getSourceTables(db, "select * from customers"); + expect(updatedCustomerTables.contains('_customers'), true); + }); + + test('should complete refresh schema after transaction', () async { + var completer1 = Completer(); + var transactionCompleted = false; + + final db = await testUtils.setupDatabase(path: path); + await createTables(db); + + // Start a read transaction + db.readTransaction((tx) async { + completer1.complete(); + await tx.get('select test_sleep(2000)'); + + transactionCompleted = true; + }); + + // Wait for the transaction to start + await completer1.future; + + var refreshSchemaFuture = db.refreshSchema(); + + // Setup check that refreshSchema completes after the transaction has completed + var refreshAfterTransaction = false; + refreshSchemaFuture.then((_) { + if (transactionCompleted) { + refreshAfterTransaction = true; + } + }); + + await refreshSchemaFuture; + + expect(refreshAfterTransaction, isTrue, + reason: 'refreshSchema completed before transaction finished'); + + // Sanity check + expect(transactionCompleted, isTrue, + reason: 'Transaction did not complete as expected'); + }); + }); +} + +// For some reason, future.ignore() doesn't actually ignore errors in these tests. +void ignore(Future future) { + future.then((_) {}, onError: (_) {}); +} From f90b7ebcbd85cccadf132d768c3272429030a519 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Mon, 19 Aug 2024 10:51:57 +0200 Subject: [PATCH 3/9] Cleanup of function comment. --- .../sqlite_async/lib/src/native/database/connection_pool.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/sqlite_async/lib/src/native/database/connection_pool.dart b/packages/sqlite_async/lib/src/native/database/connection_pool.dart index 9667907..bf44a32 100644 --- a/packages/sqlite_async/lib/src/native/database/connection_pool.dart +++ b/packages/sqlite_async/lib/src/native/database/connection_pool.dart @@ -59,7 +59,7 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { /// /// This function first locks all read and write connections, collecting their /// contexts. It then executes the provided [callback] function on each of these - /// contexts. After the [callback] completex for each context, the locks are released + /// contexts. After the [callback] completes for each context, the locks are released. /// /// Example usage: /// ```dart @@ -68,8 +68,6 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { /// await ctx.execute('PRAGMA schema_version'); /// }); /// ``` - /// - /// exclusiveLock( Future Function(SqliteReadContext tx) callback, ) async { From 03bca2914d37bbe427940ce8ad321b75e93a63ee Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Mon, 19 Aug 2024 11:08:56 +0200 Subject: [PATCH 4/9] Updated changelog. --- packages/drift_sqlite_async/pubspec.yaml | 2 +- packages/sqlite_async/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/drift_sqlite_async/pubspec.yaml b/packages/drift_sqlite_async/pubspec.yaml index eda5f50..842af6e 100644 --- a/packages/drift_sqlite_async/pubspec.yaml +++ b/packages/drift_sqlite_async/pubspec.yaml @@ -15,7 +15,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: drift: ">=2.15.0 <2.19.0" - sqlite_async: ^0.8.2 + sqlite_async: ^0.8.1 dev_dependencies: build_runner: ^2.4.8 drift_dev: ">=2.15.0 <2.19.0" diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index 65580f3..c5b8d1f 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.2 -- Added `refreshSchema()` to `SqliteConnection` and its implementations, allowing queries and watch calls to work against update schemas. +- Added `refreshSchema()` and `exclusiveLock()` to `SqliteDatabaseMixin`, allowing queries and watch calls to work against updated schemas. ## 0.8.1 From 7bb71be337b3793bef703921cbda211475622061 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Mon, 19 Aug 2024 12:14:11 +0200 Subject: [PATCH 5/9] Removed exclusiveLock, simplified implementation of refreshSchema. --- packages/sqlite_async/CHANGELOG.md | 2 +- .../lib/src/common/sqlite_database.dart | 15 ---- .../src/native/database/connection_pool.dart | 72 +++---------------- .../database/native_sqlite_database.dart | 5 +- .../sqlite_async/lib/src/sqlite_queries.dart | 6 ++ 5 files changed, 20 insertions(+), 80 deletions(-) diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index c5b8d1f..e0de42b 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.2 -- Added `refreshSchema()` and `exclusiveLock()` to `SqliteDatabaseMixin`, allowing queries and watch calls to work against updated schemas. +- Added `refreshSchema()`, allowing queries and watch calls to work against updated schemas. ## 0.8.1 diff --git a/packages/sqlite_async/lib/src/common/sqlite_database.dart b/packages/sqlite_async/lib/src/common/sqlite_database.dart index 02f4580..f8e0be5 100644 --- a/packages/sqlite_async/lib/src/common/sqlite_database.dart +++ b/packages/sqlite_async/lib/src/common/sqlite_database.dart @@ -38,21 +38,6 @@ mixin SqliteDatabaseMixin implements SqliteConnection, SqliteQueries { /// /// Use this to access the database in background isolates. IsolateConnectionFactory isolateConnectionFactory(); - - /// TODO Improve on this definition by supporting a writeable context. - Future exclusiveLock( - Future Function(SqliteReadContext ctx) callback, - ) { - return writeLock(callback); - } - - /// Ensures that all connections are aware of the latest schema changes applied (if any). - /// Queries and watch calls can potentially use outdated schema information after a schema update. - Future refreshSchema() { - return exclusiveLock((ctx) async { - return ctx.get("PRAGMA table_info('sqlite_master')"); - }); - } } /// A SQLite database instance. diff --git a/packages/sqlite_async/lib/src/native/database/connection_pool.dart b/packages/sqlite_async/lib/src/native/database/connection_pool.dart index bf44a32..9521b34 100644 --- a/packages/sqlite_async/lib/src/native/database/connection_pool.dart +++ b/packages/sqlite_async/lib/src/native/database/connection_pool.dart @@ -54,67 +54,6 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { _writeConnection?.updates?.forEach(updatesController.add); } - /// Executes a provided callback function exclusively across all read and - /// write connections in the pool. - /// - /// This function first locks all read and write connections, collecting their - /// contexts. It then executes the provided [callback] function on each of these - /// contexts. After the [callback] completes for each context, the locks are released. - /// - /// Example usage: - /// ```dart - /// await runExclusive((ctx) async { - /// // Perform some database operation with the ctx - /// await ctx.execute('PRAGMA schema_version'); - /// }); - /// ``` - exclusiveLock( - Future Function(SqliteReadContext tx) callback, - ) async { - final List> completers = []; - final List> releasers = []; - - for (final read in _allReadConnections) { - final completer = Completer(); - - completers.add(completer); - read.readLock((ctx) async { - completer.complete(ctx); - - final releaser = Completer(); - releasers.add(releaser); - - // Keep this active, close the context when finished - await releaser.future; - }); - } - - final writeCompleter = Completer(); - completers.add(writeCompleter); - _writeConnection?.writeLock((ctx) async { - writeCompleter.complete(ctx); - - final releaser = Completer(); - releasers.add(releaser); - await releaser.future; - }); - - // Get all the connection contexts and execute the callback on each of them - final List contexts = []; - for (final completer in completers) { - contexts.add(await completer.future); - } - - for (final c in contexts) { - await callback(c); - } - - // Release all the releasers - for (final r in releasers) { - r.complete(); - } - } - /// Returns true if the _write_ connection is currently in autocommit mode. @override Future getAutoCommit() async { @@ -282,6 +221,17 @@ class SqliteConnectionPool with SqliteQueries implements SqliteConnection { // read-only connections first. await _writeConnection?.close(); } + + @override + Future refreshSchema() async { + final toRefresh = _allReadConnections.toList(); + + await _writeConnection?.refreshSchema(); + + for (var connection in toRefresh) { + await connection.refreshSchema(); + } + } } typedef ReadCallback = Future Function(SqliteReadContext tx); diff --git a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart index 3557859..5cb60f3 100644 --- a/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart +++ b/packages/sqlite_async/lib/src/native/database/native_sqlite_database.dart @@ -168,8 +168,7 @@ class SqliteDatabaseImpl } @override - Future exclusiveLock( - Future Function(SqliteReadContext ctx) callback) { - return _pool.exclusiveLock(callback); + Future refreshSchema() { + return _pool.refreshSchema(); } } diff --git a/packages/sqlite_async/lib/src/sqlite_queries.dart b/packages/sqlite_async/lib/src/sqlite_queries.dart index d0eab7a..9343931 100644 --- a/packages/sqlite_async/lib/src/sqlite_queries.dart +++ b/packages/sqlite_async/lib/src/sqlite_queries.dart @@ -137,4 +137,10 @@ mixin SqliteQueries implements SqliteWriteContext, SqliteConnection { return tx.executeBatch(sql, parameterSets); }); } + + /// Ensures that all connections are aware of the latest schema changes applied (if any). + /// Queries and watch calls can potentially use outdated schema information after a schema update. + Future refreshSchema() { + return get("PRAGMA table_info('sqlite_master')"); + } } From 4fb4228f5aa064293e00c3ae5ecfce5b020e065a Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 20 Aug 2024 10:08:46 +0200 Subject: [PATCH 6/9] squashed --- packages/sqlite_async/CHANGELOG.md | 4 ---- packages/sqlite_async/lib/src/sqlite_connection.dart | 4 ++++ packages/sqlite_async/lib/src/sqlite_queries.dart | 3 +-- packages/sqlite_async/pubspec.yaml | 2 +- packages/sqlite_async/test/native/schema_test.dart | 5 ----- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index e0de42b..558a50a 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.8.2 - -- Added `refreshSchema()`, allowing queries and watch calls to work against updated schemas. - ## 0.8.1 - Added Navigator locks for web `Mutex`s. diff --git a/packages/sqlite_async/lib/src/sqlite_connection.dart b/packages/sqlite_async/lib/src/sqlite_connection.dart index f92d318..f1b721a 100644 --- a/packages/sqlite_async/lib/src/sqlite_connection.dart +++ b/packages/sqlite_async/lib/src/sqlite_connection.dart @@ -130,6 +130,10 @@ abstract class SqliteConnection extends SqliteWriteContext { Future close(); + /// Ensures that all connections are aware of the latest schema changes applied (if any). + /// Queries and watch calls can potentially use outdated schema information after a schema update. + Future refreshSchema(); + /// Returns true if the connection is closed @override bool get closed; diff --git a/packages/sqlite_async/lib/src/sqlite_queries.dart b/packages/sqlite_async/lib/src/sqlite_queries.dart index 9343931..f777fc9 100644 --- a/packages/sqlite_async/lib/src/sqlite_queries.dart +++ b/packages/sqlite_async/lib/src/sqlite_queries.dart @@ -138,8 +138,7 @@ mixin SqliteQueries implements SqliteWriteContext, SqliteConnection { }); } - /// Ensures that all connections are aware of the latest schema changes applied (if any). - /// Queries and watch calls can potentially use outdated schema information after a schema update. + @override Future refreshSchema() { return get("PRAGMA table_info('sqlite_master')"); } diff --git a/packages/sqlite_async/pubspec.yaml b/packages/sqlite_async/pubspec.yaml index e151aa8..58eb5df 100644 --- a/packages/sqlite_async/pubspec.yaml +++ b/packages/sqlite_async/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlite_async description: High-performance asynchronous interface for SQLite on Dart and Flutter. -version: 0.8.2 +version: 0.8.1 repository: https://github.com/powersync-ja/sqlite_async.dart environment: sdk: ">=3.4.0 <4.0.0" diff --git a/packages/sqlite_async/test/native/schema_test.dart b/packages/sqlite_async/test/native/schema_test.dart index 96f4d69..c358402 100644 --- a/packages/sqlite_async/test/native/schema_test.dart +++ b/packages/sqlite_async/test/native/schema_test.dart @@ -96,8 +96,3 @@ void main() { }); }); } - -// For some reason, future.ignore() doesn't actually ignore errors in these tests. -void ignore(Future future) { - future.then((_) {}, onError: (_) {}); -} From 31c6989e07a67d6ebea7258987d298769a1724a2 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 20 Aug 2024 10:21:31 +0200 Subject: [PATCH 7/9] chore(release): publish packages - sqlite_async@0.8.2 - drift_sqlite_async@0.1.0-alpha.5 --- CHANGELOG.md | 28 ++++++++++++++++++++++++ melos.yaml | 6 +++++ packages/drift_sqlite_async/CHANGELOG.md | 4 ++++ packages/drift_sqlite_async/pubspec.yaml | 4 ++-- packages/sqlite_async/CHANGELOG.md | 4 ++++ packages/sqlite_async/pubspec.yaml | 2 +- 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 024eee3..3b0e6a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 2024-08-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`sqlite_async` - `v0.8.2`](#sqlite_async---v082) + - [`drift_sqlite_async` - `v0.1.0-alpha.5`](#drift_sqlite_async---v010-alpha5) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `drift_sqlite_async` - `v0.1.0-alpha.5` + +--- + +#### `sqlite_async` - `v0.8.2` + + - **FEAT**: Added `refreshSchema()`, allowing queries and watch calls to work against updated schemas. + + ## 2024-07-10 ### Changes diff --git a/melos.yaml b/melos.yaml index 19d45ad..4a965e5 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,6 +3,12 @@ name: sqlite_async_monorepo packages: - packages/** +command: + version: + changelog: false + packageFilters: + noPrivate: true + scripts: prepare: melos bootstrap && melos prepare:compile:webworker && melos prepare:sqlite:wasm diff --git a/packages/drift_sqlite_async/CHANGELOG.md b/packages/drift_sqlite_async/CHANGELOG.md index cb42533..c2b7079 100644 --- a/packages/drift_sqlite_async/CHANGELOG.md +++ b/packages/drift_sqlite_async/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0-alpha.5 + + - Update a dependency to the latest release. + ## 0.1.0-alpha.4 - Import `sqlite3_common` instead of `sqlite3` for web support. diff --git a/packages/drift_sqlite_async/pubspec.yaml b/packages/drift_sqlite_async/pubspec.yaml index 842af6e..ccef204 100644 --- a/packages/drift_sqlite_async/pubspec.yaml +++ b/packages/drift_sqlite_async/pubspec.yaml @@ -1,5 +1,5 @@ name: drift_sqlite_async -version: 0.1.0-alpha.4 +version: 0.1.0-alpha.5 homepage: https://github.com/powersync-ja/sqlite_async.dart repository: https://github.com/powersync-ja/sqlite_async.dart description: Use Drift with a sqlite_async database, allowing both to be used in the same application. @@ -15,7 +15,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: drift: ">=2.15.0 <2.19.0" - sqlite_async: ^0.8.1 + sqlite_async: ^0.8.2 dev_dependencies: build_runner: ^2.4.8 drift_dev: ">=2.15.0 <2.19.0" diff --git a/packages/sqlite_async/CHANGELOG.md b/packages/sqlite_async/CHANGELOG.md index 558a50a..610273b 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.2 + +- **FEAT**: Added `refreshSchema()`, allowing queries and watch calls to work against updated schemas. + ## 0.8.1 - Added Navigator locks for web `Mutex`s. diff --git a/packages/sqlite_async/pubspec.yaml b/packages/sqlite_async/pubspec.yaml index 58eb5df..e151aa8 100644 --- a/packages/sqlite_async/pubspec.yaml +++ b/packages/sqlite_async/pubspec.yaml @@ -1,6 +1,6 @@ name: sqlite_async description: High-performance asynchronous interface for SQLite on Dart and Flutter. -version: 0.8.1 +version: 0.8.2 repository: https://github.com/powersync-ja/sqlite_async.dart environment: sdk: ">=3.4.0 <4.0.0" From 204d5e8dc38b8284ccfce3686f44447961dd1ad9 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 20 Aug 2024 10:42:47 +0200 Subject: [PATCH 8/9] Increased pana score threshold temporarily. --- melos.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/melos.yaml b/melos.yaml index 4a965e5..4762c19 100644 --- a/melos.yaml +++ b/melos.yaml @@ -34,7 +34,7 @@ scripts: analyze:packages:pana: description: Analyze Dart packages with Pana - exec: dart pub global run pana --no-warning --exit-code-threshold 0 + exec: dart pub global run pana --no-warning --exit-code-threshold 10 packageFilters: noPrivate: true From 05101e83567488dac4c471da022be230a2755346 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 20 Aug 2024 10:47:42 +0200 Subject: [PATCH 9/9] Added comment to pana threshold alteration. --- melos.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/melos.yaml b/melos.yaml index 4762c19..627a740 100644 --- a/melos.yaml +++ b/melos.yaml @@ -32,6 +32,7 @@ scripts: description: Analyze Dart code in packages. run: dart analyze packages --fatal-infos + # TODO: Temporarily setting the exit-code-threshold to 10 until drift_sqlite_async dependencies are updated. analyze:packages:pana: description: Analyze Dart packages with Pana exec: dart pub global run pana --no-warning --exit-code-threshold 10