Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
57 changes: 57 additions & 0 deletions PowerSyncKotlin/src/appleMain/kotlin/com/powersync/Locks.kt
Original file line number Diff line number Diff line change
@@ -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<Any> {
override fun execute(context: ConnectionContext): Any = handleLockResult(handler(context))
}

public class TransactionContextWrapper(
private val handler: (context: PowerSyncTransaction) -> PowerSyncResult,
) : ThrowableTransactionCallback<Any> {
override fun execute(transaction: PowerSyncTransaction): Any = handleLockResult(handler(transaction))
}

public fun wrapContextHandler(handler: (context: ConnectionContext) -> PowerSyncResult): ThrowableLockCallback<Any> =
LockContextWrapper(handler)

public fun wrapTransactionContextHandler(handler: (context: PowerSyncTransaction) -> PowerSyncResult): ThrowableTransactionCallback<Any> =
TransactionContextWrapper(handler)
4 changes: 4 additions & 0 deletions PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,7 @@ internal class InternalDatabaseImpl(
withContext(dbContext) {
runWrapped {
readPool.withConnection {
catchSwiftExceptions {
callback(it)
}
callback(it)
}
}
}
Expand All @@ -196,23 +194,19 @@ internal class InternalDatabaseImpl(
override suspend fun <R> readTransaction(callback: ThrowableTransactionCallback<R>): R =
internalReadLock {
it.transactor.transactionWithResult(noEnclosing = true) {
catchSwiftExceptions {
callback.execute(
PowerSyncTransactionImpl(
it.driver,
),
)
}
callback.execute(
PowerSyncTransactionImpl(
it.driver,
),
)
}
}

private suspend fun <R> internalWriteLock(callback: (TransactorDriver) -> R): R =
withContext(dbContext) {
writeLockMutex.withLock {
runWrapped {
catchSwiftExceptions {
callback(writeConnection)
}
callback(writeConnection)
}.also {
// Trigger watched queries
// Fire updates inside the write lock
Expand All @@ -229,31 +223,17 @@ internal class InternalDatabaseImpl(
override suspend fun <R> writeTransaction(callback: ThrowableTransactionCallback<R>): 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<Set<String>> = writeConnection.driver.updatesOnTables()

// Unfortunately Errors can't be thrown from Swift SDK callbacks.
// These are currently returned and should be thrown here.
private fun <R> catchSwiftExceptions(action: () -> R): R {
val result = action()

if (result is PowerSyncException) {
throw result
}
return result
}

private suspend fun getSourceTables(
sql: String,
parameters: List<Any?>?,
Expand Down
Loading