From 5b451b0825a86ba55ac62ba665a869bbe0ae4306 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 21 Aug 2025 11:19:37 +0200 Subject: [PATCH 1/2] Add Swift lock exception handler utils --- CHANGELOG.md | 1 + .../appleMain/kotlin/com/powersync/Locks.kt | 57 +++++++++++++++++++ .../src/appleMain/kotlin/com/powersync/SDK.kt | 4 ++ .../db/internal/InternalDatabaseImpl.kt | 44 ++++---------- 4 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 PowerSyncKotlin/src/appleMain/kotlin/com/powersync/Locks.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index db1f144a..688a54d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ to upload multiple transactions in a batch. * Fix modifying severity of the global Kermit logger * Add `PowerSync` tag for the logs +* [INTERNAL] Added helpers for Swift read and write lock exception handling. ## 1.4.0 diff --git a/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/Locks.kt b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/Locks.kt new file mode 100644 index 00000000..dbb5297c --- /dev/null +++ b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/Locks.kt @@ -0,0 +1,57 @@ +package com.powersync + +import com.powersync.db.ThrowableLockCallback +import com.powersync.db.ThrowableTransactionCallback +import com.powersync.db.internal.ConnectionContext +import com.powersync.db.internal.PowerSyncTransaction + +/** + * The Kotlin [Result] type is an inline class which cannot be used on the Swift side. + * This declares something similar which will help to bridge exceptions to Kotlin. + * SKIEE doesn't handle generics well. + * Making the Result type generic will cause the return type to be casted to "Any", but + * also add restrictions that the generic should extend an object - which causes issues when returning + * primitive types like `Int` or `String`. + */ +public sealed class PowerSyncResult { + public data class Success( + val value: Any, + ) : PowerSyncResult() + + public data class Failure( + val exception: PowerSyncException, + ) : PowerSyncResult() +} + +// Throws the [PowerSyncException] if the result is a failure, or returns the value if it is a success. +// We throw the exception on behalf of the Swift SDK. +@Throws(PowerSyncException::class) +private fun handleLockResult(result: PowerSyncResult): Any { + when (result) { + is PowerSyncResult.Failure -> { + throw result.exception + } + + is PowerSyncResult.Success -> { + return result.value + } + } +} + +public class LockContextWrapper( + private val handler: (context: ConnectionContext) -> PowerSyncResult, +) : ThrowableLockCallback { + override fun execute(context: ConnectionContext): Any = handleLockResult(handler(context)) +} + +public class TransactionContextWrapper( + private val handler: (context: PowerSyncTransaction) -> PowerSyncResult, +) : ThrowableTransactionCallback { + override fun execute(transaction: PowerSyncTransaction): Any = handleLockResult(handler(transaction)) +} + +public fun wrapContextHandler(handler: (context: ConnectionContext) -> PowerSyncResult): ThrowableLockCallback = + LockContextWrapper(handler) + +public fun wrapTransactionContextHandler(handler: (context: PowerSyncTransaction) -> PowerSyncResult): ThrowableTransactionCallback = + TransactionContextWrapper(handler) diff --git a/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt index 6204de80..acd71bbb 100644 --- a/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt +++ b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt @@ -2,6 +2,10 @@ package com.powersync +import com.powersync.db.ThrowableLockCallback +import com.powersync.db.ThrowableTransactionCallback +import com.powersync.db.internal.ConnectionContext +import com.powersync.db.internal.PowerSyncTransaction import com.powersync.sync.SyncClientConfiguration import com.powersync.sync.SyncOptions import io.ktor.client.plugins.logging.LogLevel diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt index af47b95f..f687ee17 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt @@ -181,9 +181,7 @@ internal class InternalDatabaseImpl( withContext(dbContext) { runWrapped { readPool.withConnection { - catchSwiftExceptions { - callback(it) - } + callback(it) } } } @@ -196,13 +194,11 @@ internal class InternalDatabaseImpl( override suspend fun readTransaction(callback: ThrowableTransactionCallback): R = internalReadLock { it.transactor.transactionWithResult(noEnclosing = true) { - catchSwiftExceptions { - callback.execute( - PowerSyncTransactionImpl( - it.driver, - ), - ) - } + callback.execute( + PowerSyncTransactionImpl( + it.driver, + ), + ) } } @@ -210,9 +206,7 @@ internal class InternalDatabaseImpl( withContext(dbContext) { writeLockMutex.withLock { runWrapped { - catchSwiftExceptions { - callback(writeConnection) - } + callback(writeConnection) }.also { // Trigger watched queries // Fire updates inside the write lock @@ -229,31 +223,17 @@ internal class InternalDatabaseImpl( override suspend fun writeTransaction(callback: ThrowableTransactionCallback): R = internalWriteLock { it.transactor.transactionWithResult(noEnclosing = true) { - // Need to catch Swift exceptions here for Rollback - catchSwiftExceptions { - callback.execute( - PowerSyncTransactionImpl( - it.driver, - ), - ) - } + callback.execute( + PowerSyncTransactionImpl( + it.driver, + ), + ) } } // Register callback for table updates on a specific table override fun updatesOnTables(): SharedFlow> = writeConnection.driver.updatesOnTables() - // Unfortunately Errors can't be thrown from Swift SDK callbacks. - // These are currently returned and should be thrown here. - private fun catchSwiftExceptions(action: () -> R): R { - val result = action() - - if (result is PowerSyncException) { - throw result - } - return result - } - private suspend fun getSourceTables( sql: String, parameters: List?, From 15667770118f1d472fd2f660ecff29c728097b79 Mon Sep 17 00:00:00 2001 From: stevensJourney Date: Thu, 21 Aug 2025 13:07:10 +0200 Subject: [PATCH 2/2] Remove unused imports --- PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt | 4 ---- .../kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt | 1 - 2 files changed, 5 deletions(-) diff --git a/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt index acd71bbb..6204de80 100644 --- a/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt +++ b/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt @@ -2,10 +2,6 @@ package com.powersync -import com.powersync.db.ThrowableLockCallback -import com.powersync.db.ThrowableTransactionCallback -import com.powersync.db.internal.ConnectionContext -import com.powersync.db.internal.PowerSyncTransaction import com.powersync.sync.SyncClientConfiguration import com.powersync.sync.SyncOptions import io.ktor.client.plugins.logging.LogLevel diff --git a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt b/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt index f687ee17..fc08305c 100644 --- a/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt +++ b/core/src/commonMain/kotlin/com/powersync/db/internal/InternalDatabaseImpl.kt @@ -2,7 +2,6 @@ package com.powersync.db.internal import app.cash.sqldelight.db.SqlPreparedStatement import com.powersync.DatabaseDriverFactory -import com.powersync.PowerSyncException import com.powersync.db.SqlCursor import com.powersync.db.ThrowableLockCallback import com.powersync.db.ThrowableTransactionCallback