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..627a740 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 @@ -26,9 +32,10 @@ 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 0 + exec: dart pub global run pana --no-warning --exit-code-threshold 10 packageFilters: noPrivate: true 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 cd48d89..610273b 100644 --- a/packages/sqlite_async/CHANGELOG.md +++ b/packages/sqlite_async/CHANGELOG.md @@ -1,6 +1,10 @@ +## 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. +- Added Navigator locks for web `Mutex`s. ## 0.8.0 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..9521b34 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,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 998fb79..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 @@ -166,4 +166,9 @@ class SqliteDatabaseImpl readOnly: false, openFactory: openFactory); } + + @override + Future refreshSchema() { + return _pool.refreshSchema(); + } } 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 d0eab7a..f777fc9 100644 --- a/packages/sqlite_async/lib/src/sqlite_queries.dart +++ b/packages/sqlite_async/lib/src/sqlite_queries.dart @@ -137,4 +137,9 @@ mixin SqliteQueries implements SqliteWriteContext, SqliteConnection { return tx.executeBatch(sql, parameterSets); }); } + + @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 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..c358402 --- /dev/null +++ b/packages/sqlite_async/test/native/schema_test.dart @@ -0,0 +1,98 @@ +@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'); + }); + }); +}