diff --git a/packages/powersync_core/CHANGELOG.md b/packages/powersync_core/CHANGELOG.md index 4ba23772..470b451c 100644 --- a/packages/powersync_core/CHANGELOG.md +++ b/packages/powersync_core/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.7.0-dev + +- Update PowerSync core extension to 0.4.8. +- `disconnectAndClear()`: Add `soft` parameter to only delete public data, allowing it to be reconstructed quickly. +- Raw tables: Add `clear` parameter to clear raw tables in `disconnectAndClear()`. + ## 1.6.1 - Web: Fix decoding sync streams on status. diff --git a/packages/powersync_core/lib/src/database/core_version.dart b/packages/powersync_core/lib/src/database/core_version.dart index 1a3ca3da..ff4aa70a 100644 --- a/packages/powersync_core/lib/src/database/core_version.dart +++ b/packages/powersync_core/lib/src/database/core_version.dart @@ -63,7 +63,7 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) { // - scripts/download_core_binary_demos.dart // - packages/sqlite3_wasm_build/build.sh // - Android and Darwin (CocoaPods and SwiftPM) in powersync_flutter_libs - static const minimum = PowerSyncCoreVersion((0, 4, 6)); + static const minimum = PowerSyncCoreVersion((0, 4, 8)); /// The first version of the core extensions that this version of the Dart /// SDK doesn't support. diff --git a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart index fd722a2a..2d254cf9 100644 --- a/packages/powersync_core/lib/src/database/powersync_db_mixin.dart +++ b/packages/powersync_core/lib/src/database/powersync_db_mixin.dart @@ -245,17 +245,35 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection { /// Disconnect and clear the database. /// - /// Use this when logging out. + /// Clearing the database is useful when a user logs out, to ensure another + /// user logging in later would not see previous data. /// /// The database can still be queried after this is called, but the tables /// would be empty. /// - /// To preserve data in local-only tables, set [clearLocal] to false. - Future disconnectAndClear({bool clearLocal = true}) async { + /// To preserve data in local-only tables, set [clearLocal] to `false`. + /// + /// A [soft] clear deletes publicly visible data, but keeps internal copies of + /// data synced in the database. This usually means that if the same user logs + /// out and back in again, the first sync is very fast because all internal + /// data is still available. WHen a different user logs in, no old data would + /// be visible at any point. + /// Using soft deletes is recommended where it's not a security issue that old + /// data could be reconstructible from the database. + Future disconnectAndClear( + {bool clearLocal = true, bool soft = false}) async { await disconnect(); await writeTransaction((tx) async { - await tx.execute('select powersync_clear(?)', [clearLocal ? 1 : 0]); + var flags = 0; + if (clearLocal) { + flags |= 1; // MASK_CLEAR_LOCAL + } + if (soft) { + flags |= 2; // MASK_SOFT_CLEAR + } + + await tx.execute('select powersync_clear(?)', [flags]); }); // The data has been deleted - reset these setStatus(SyncStatus(lastSyncedAt: null, hasSynced: false)); diff --git a/packages/powersync_core/lib/src/schema.dart b/packages/powersync_core/lib/src/schema.dart index 1289cae0..e46b6168 100644 --- a/packages/powersync_core/lib/src/schema.dart +++ b/packages/powersync_core/lib/src/schema.dart @@ -1,3 +1,6 @@ +/// @docImport 'database/powersync_db_mixin.dart'; +library; + import 'crud.dart'; import 'schema_logic.dart'; @@ -368,16 +371,25 @@ final class RawTable { /// used here must all be [PendingStatementValue.id]. final PendingStatement delete; + /// An optional SQL statement to run when a PowerSync database is cleared. + /// + /// When this value is unset, clearing the database (via + /// [PowerSyncDatabaseMixin.disconnectAndClear]) would not affect the raw + /// table. + final String? clear; + const RawTable({ required this.name, required this.put, required this.delete, + this.clear, }); Map toJson() => { 'name': name, 'put': put, 'delete': delete, + 'clear': clear, }; } diff --git a/packages/powersync_core/test/disconnect_test.dart b/packages/powersync_core/test/disconnect_test.dart index 86f18130..3b3138ca 100644 --- a/packages/powersync_core/test/disconnect_test.dart +++ b/packages/powersync_core/test/disconnect_test.dart @@ -69,5 +69,61 @@ void main() { final changes = await changesFuture; expect(changes.first, equals(UpdateNotification({'customers'}))); }); + + test('soft clear', () async { + final db = await testUtils.setupPowerSync(path: path, schema: testSchema); + addTearDown(db.close); + + await db.execute( + 'INSERT INTO customers (id, name) VALUES(uuid(), ?)', ['testuser']); + await db.execute( + 'INSERT INTO ps_buckets (name, last_applied_op) VALUES (?, ?)', + ['bkt', 10], + ); + + // Doing a soft-clear should delete data but keep the bucket around. + await db.disconnectAndClear(soft: true); + expect(await db.getAll('SELECT * FROM ps_buckets'), hasLength(1)); + + // Doing a default clear also deletes buckets. + await db.disconnectAndClear(); + expect(await db.getAll('SELECT * FROM ps_buckets'), isEmpty); + }); + + test('clear raw tables', () async { + final db = await testUtils.setupPowerSync( + path: path, + schema: Schema( + rawTables: [ + RawTable( + name: 'lists', + put: PendingStatement( + sql: 'INSERT OR REPLACE INTO lists (id, name) VALUES (?, ?)', + params: [ + PendingStatementValue.id(), + PendingStatementValue.column('name') + ], + ), + delete: PendingStatement( + sql: 'DELETE FROM lists WHERE id = ?', + params: [PendingStatementValue.id()], + ), + clear: 'DELETE FROM lists', + ), + ], + [], + ), + ); + addTearDown(db.close); + + await db.execute( + 'CREATE TABLE lists (id TEXT NOT NULL PRIMARY KEY, name TEXT)'); + await db + .execute('INSERT INTO lists (id, name) VALUES (uuid(), ?)', ['list']); + + expect(await db.getAll('SELECT * FROM lists'), hasLength(1)); + await db.disconnectAndClear(); + expect(await db.getAll('SELECT * FROM lists'), isEmpty); + }); }); } diff --git a/packages/powersync_flutter_libs/android/build.gradle b/packages/powersync_flutter_libs/android/build.gradle index d6c0e3c1..a50d2aa4 100644 --- a/packages/powersync_flutter_libs/android/build.gradle +++ b/packages/powersync_flutter_libs/android/build.gradle @@ -50,5 +50,5 @@ android { } dependencies { - implementation 'com.powersync:powersync-sqlite-core:0.4.6' + implementation 'com.powersync:powersync-sqlite-core:0.4.8' } diff --git a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec index b93848a8..adeb6a16 100644 --- a/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec +++ b/packages/powersync_flutter_libs/darwin/powersync_flutter_libs.podspec @@ -25,7 +25,7 @@ A new Flutter FFI plugin project. s.osx.deployment_target = '10.15' # NOTE: Always update Package.swift as well when updating this! - s.dependency "powersync-sqlite-core", "~> 0.4.6" + s.dependency "powersync-sqlite-core", "~> 0.4.8" # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/powersync_flutter_libs/linux/CMakeLists.txt b/packages/powersync_flutter_libs/linux/CMakeLists.txt index 7c94cbbf..c2ff5328 100644 --- a/packages/powersync_flutter_libs/linux/CMakeLists.txt +++ b/packages/powersync_flutter_libs/linux/CMakeLists.txt @@ -29,9 +29,9 @@ set(CORE_FILE_NAME "libpowersync.so") set(POWERSYNC_ARCH ${CMAKE_SYSTEM_PROCESSOR}) if (${POWERSYNC_ARCH} MATCHES "x86_64" OR ${POWERSYNC_ARCH} MATCHES "AMD64") - set(CORE_FILE_NAME "libpowersync_x64.so") + set(CORE_FILE_NAME "libpowersync_x64.linux.so") elseif (${POWERSYNC_ARCH} MATCHES "^arm64" OR ${POWERSYNC_ARCH} MATCHES "^armv8") - set(CORE_FILE_NAME "libpowersync_aarch64.so") + set(CORE_FILE_NAME "libpowersync_aarch64.linux.so") endif () set(POWERSYNC_FILE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${CORE_FILE_NAME}") diff --git a/packages/sqlite3_wasm_build/build.sh b/packages/sqlite3_wasm_build/build.sh index eefbb4ae..ebb25d80 100755 --- a/packages/sqlite3_wasm_build/build.sh +++ b/packages/sqlite3_wasm_build/build.sh @@ -2,7 +2,7 @@ set -e SQLITE_VERSION="2.9.0" -POWERSYNC_CORE_VERSION="0.4.6" +POWERSYNC_CORE_VERSION="0.4.8" SQLITE_PATH="sqlite3.dart" if [ -d "$SQLITE_PATH" ]; then diff --git a/scripts/download_core_binary_demos.dart b/scripts/download_core_binary_demos.dart index f5453ce1..e02dd671 100644 --- a/scripts/download_core_binary_demos.dart +++ b/scripts/download_core_binary_demos.dart @@ -3,14 +3,14 @@ import 'dart:io'; final coreUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.6'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.8'; void main() async { final powersyncLibsLinuxPath = "packages/powersync_flutter_libs/linux"; final powersyncLibsWindowsPath = "packages/powersync_flutter_libs/windows"; - final linuxArm64FileName = "libpowersync_aarch64.so"; - final linuxX64FileName = "libpowersync_x64.so"; + final linuxArm64FileName = "libpowersync_aarch64.linux.so"; + final linuxX64FileName = "libpowersync_x64.linux.so"; final windowsX64FileName = "powersync_x64.dll"; // Download dynamic library diff --git a/scripts/init_powersync_core_binary.dart b/scripts/init_powersync_core_binary.dart index 0c985895..c7eaa6c5 100644 --- a/scripts/init_powersync_core_binary.dart +++ b/scripts/init_powersync_core_binary.dart @@ -6,7 +6,7 @@ import 'dart:io'; import 'package:melos/melos.dart'; final sqliteUrl = - 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.6'; + 'https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v0.4.8'; void main() async { final sqliteCoreFilename = getLibraryForPlatform(); @@ -72,13 +72,13 @@ Future downloadFile(String url, String savePath) async { String getLibraryForPlatform() { switch (Abi.current()) { case Abi.macosArm64: - return 'libpowersync_aarch64.dylib'; + return 'libpowersync_aarch64.macos.dylib'; case Abi.macosX64: - return 'libpowersync_x64.dylib'; + return 'libpowersync_x64.macos.dylib'; case Abi.linuxX64: - return 'libpowersync_x64.so'; + return 'libpowersync_x64.linux.so'; case Abi.linuxArm64: - return 'libpowersync_aarch64.so'; + return 'libpowersync_aarch64.linux.so'; case Abi.windowsX64: return 'powersync_x64.dll'; case Abi.windowsArm64: