Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -181,9 +180,7 @@ internal class InternalDatabaseImpl(
withContext(dbContext) {
runWrapped {
readPool.withConnection {
catchSwiftExceptions {
callback(it)
}
callback(it)
}
}
}
Expand All @@ -196,23 +193,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 +222,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