From 0f0913180b385b08fc94dd7c9ec0801d81211a26 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 27 Oct 2025 09:36:22 +0100 Subject: [PATCH 1/2] Update core extension to 0.4.8 --- CHANGELOG.md | 4 ++ common/build.gradle.kts | 8 +-- .../kotlin/com/powersync/DatabaseTest.kt | 56 +++++++++++++++++++ .../kotlin/com/powersync/PowerSyncDatabase.kt | 16 +++++- .../com/powersync/db/PowerSyncDatabaseImpl.kt | 15 ++++- .../com/powersync/db/schema/RawTable.kt | 5 ++ .../kotlin/com/powersync/ExtractLib.kt | 4 +- gradle/libs.versions.toml | 2 +- 8 files changed, 99 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9658a30c..7f4f645b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - __POTENTIALLY BREAKING CHANGE__: If you were injecting a `DatabaseDriverFactory` into Koin or Dagger, note that the `PowerSyncDatabase()` factory method now takes a more generic `PersistentConnectionFactory`. - If you're using `PowerSyncDatabase.inMemory`, you explicitly have to import `com.powersync.inMemory` now. +- Update the PowerSync core extension to version 0.4.8. +- Add the `soft` flag to `disconnectAndClear()` which keeps an internal copy of synced data in the database, allowing + faster re-sync if a compatible token is used in the next `connect()` call. +- Add the `clear` parameter to `RawTable` to run a statement helping the core extension clear raw tables. ## 1.7.0 diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 5384236a..fd4ed4a6 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -32,13 +32,13 @@ val downloadPowersyncDesktopBinaries by tasks.registering(Download::class) { libs.versions.powersync.core .get() val linux_aarch64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.so" + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.linux.so" val linux_x64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.so" + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.linux.so" val macos_aarch64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.dylib" + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_aarch64.macos.dylib" val macos_x64 = - "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.dylib" + "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/libpowersync_x64.macos.dylib" val windows_x64 = "https://github.com/powersync-ja/powersync-sqlite-core/releases/download/v$coreVersion/powersync_x64.dll" diff --git a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt index 538c4f4b..90e53bac 100644 --- a/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt +++ b/common/src/commonIntegrationTest/kotlin/com/powersync/DatabaseTest.kt @@ -7,7 +7,11 @@ import com.powersync.db.ActiveDatabaseGroup import com.powersync.db.crud.CrudEntry import com.powersync.db.crud.CrudTransaction import com.powersync.db.getString +import com.powersync.db.schema.PendingStatement +import com.powersync.db.schema.PendingStatementParameter +import com.powersync.db.schema.RawTable import com.powersync.db.schema.Schema +import com.powersync.sync.LegacySyncImplementation import com.powersync.test.getTempDir import com.powersync.test.waitFor import com.powersync.testutils.UserRow @@ -548,4 +552,56 @@ class DatabaseTest { job.cancelAndJoin() hadOtherWrite.await() } + + @Test + fun testSoftClear() = + databaseTest { + database.execute("INSERT INTO users (id, name) VALUES (uuid(), ?)", listOf("testuser")) + database.execute("INSERT INTO ps_buckets (name, last_applied_op) VALUES (?, ?)", listOf("bkt", 10)) + + // Doing a soft-clear should delete data but keep the bucket around. + database.disconnectAndClear(soft = true) + database.getAll("SELECT name FROM ps_buckets") { it.getString("name") } shouldHaveSize 1 + + // Doing a default clear also deletes buckets + database.disconnectAndClear() + database.getAll("SELECT name FROM ps_buckets") { it.getString("name") } shouldHaveSize 0 + } + + @Test + @OptIn(ExperimentalPowerSyncAPI::class) + fun testRawTablesClear() = + databaseTest(createInitialDatabase = false) { + val db = + openDatabase( + Schema( + listOf( + RawTable( + name = "lists", + put = + PendingStatement( + "INSERT OR REPLACE INTO lists (id, name) VALUES (?, ?)", + listOf( + PendingStatementParameter.Id, + PendingStatementParameter.Column("name"), + ), + ), + delete = + PendingStatement( + "DELETE FROM lists WHERE id = ?", + listOf(PendingStatementParameter.Id), + ), + clear = "DELETE FROM lists", + ), + ), + ), + ) + + db.execute("CREATE TABLE lists (id TEXT NOT NULL PRIMARY KEY, name TEXT)") + db.execute("INSERT INTO lists (id, name) VALUES (uuid(), ?)", listOf("list")) + + db.getAll("SELECT * FROM lists") { } shouldHaveSize 1 + db.disconnectAndClear() + db.getAll("SELECT * FROM lists") { } shouldHaveSize 0 + } } diff --git a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt index 3a97f554..dc01ec67 100644 --- a/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt +++ b/common/src/commonMain/kotlin/com/powersync/PowerSyncDatabase.kt @@ -204,14 +204,26 @@ public interface PowerSyncDatabase : Queries { /** * 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. + * + * A "soft" clear deletes publicly visible tables, 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. */ @Throws(PowerSyncException::class, CancellationException::class) - public suspend fun disconnectAndClear(clearLocal: Boolean = true) + public suspend fun disconnectAndClear( + clearLocal: Boolean = true, + soft: Boolean = false, + ) /** * Close the database, releasing resources. diff --git a/common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt b/common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt index ddb8abe5..1141c127 100644 --- a/common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt @@ -461,11 +461,22 @@ internal class PowerSyncDatabaseImpl( } } - override suspend fun disconnectAndClear(clearLocal: Boolean) { + override suspend fun disconnectAndClear( + clearLocal: Boolean, + soft: Boolean, + ) { disconnect() + var flags = 0 + if (clearLocal) { + flags = flags or 1 // MASK_CLEAR_LOCAL + } + if (soft) { + flags = flags or 2 // MASK_SOFT_CLEAR + } + internalDb.writeTransaction { tx -> - tx.getOptional("SELECT powersync_clear(?)", listOf(if (clearLocal) "1" else "0")) {} + tx.getOptional("SELECT powersync_clear(?)", listOf(flags)) {} } currentStatus.update { copy(lastSyncedAt = null, hasSynced = false) } } diff --git a/common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt b/common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt index 995402b3..1dff14f5 100644 --- a/common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt +++ b/common/src/commonMain/kotlin/com/powersync/db/schema/RawTable.kt @@ -32,6 +32,10 @@ public class RawTable( * The statement to run when the sync client wants to delete a row. */ public val delete: PendingStatement, + /** + * An optional statement to run when [com.powersync.PowerSyncDatabase.disconnectAndClear] is called on the database. + */ + public val clear: String? = null, ) : BaseTable { override fun validate() { // We don't currently have any validation for raw tables @@ -42,6 +46,7 @@ public class RawTable( put("name", name) put("put", put.serialize()) put("delete", delete.serialize()) + clear?.let { put("clear", it) } } } diff --git a/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt b/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt index 48c661be..ee7ee44a 100644 --- a/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt +++ b/common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt @@ -9,8 +9,8 @@ internal fun extractLib(fileName: String): String { val os = System.getProperty("os.name").lowercase() val (prefix, extension) = when { - os.contains("nux") || os.contains("nix") || os.contains("aix") -> "lib" to "so" - os.contains("mac") -> "lib" to "dylib" + os.contains("nux") || os.contains("nix") || os.contains("aix") -> "lib" to "linux.so" + os.contains("mac") -> "lib" to "macos.dylib" os.contains("win") -> "" to "dll" else -> error("Unsupported OS: $os") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8130512..4af0337d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ serialization = "1.9.0" kotlinx-io = "0.8.0" ktor = "3.2.3" uuid = "0.8.4" -powersync-core = "0.4.6" +powersync-core = "0.4.8" turbine = "1.2.1" kotest = "5.9.1" # we can't upgrade to 6.x because that requires Java 11 or above (we need Java 8 support) From 641a3c318d5a5dd1c67465784f71545282d1d56b Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 27 Oct 2025 09:49:35 +0100 Subject: [PATCH 2/2] Prepare Kotlin release as well --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f4f645b..aaa796de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.8.0 (unreleased) +## 1.8.0 - Refactor SDK: `com.powersync:powersync-core` has an identical API, but now depends on `com.powersync:powersync-common` where most logic is implemented. diff --git a/gradle.properties b/gradle.properties index a2776781..44811ed3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ development=true RELEASE_SIGNING_ENABLED=true # Library config GROUP=com.powersync -LIBRARY_VERSION=1.7.0 +LIBRARY_VERSION=1.8.0 GITHUB_REPO=https://github.com/powersync-ja/powersync-kotlin.git # POM POM_URL=https://github.com/powersync-ja/powersync-kotlin/