-
Notifications
You must be signed in to change notification settings - Fork 19
Swift Driver Additions #262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
stevensJourney
wants to merge
33
commits into
main
Choose a base branch
from
grdb-drivers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 24 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
136e809
New unified driver interfaces
simolus3 435c7c5
Fix leaking statements
simolus3 522f4c6
Add raw connection API
simolus3 2359b48
Add changelog entry
simolus3 f4d98d5
Lease API that works better with Room
simolus3 eccb7f7
Notify updates from raw statements
simolus3 54829bc
Delete more driver stuff
simolus3 f532ba9
Fix deadlock in initialization
simolus3 c7adbab
Make addPowerSyncExtension public
simolus3 011b1da
Actually, use callbacks
simolus3 c87e079
merge main
simolus3 cb1373d
Add docs
simolus3 aa4e7bc
Fix lints
simolus3 03796e7
Add native sqlite driver
simolus3 a0682c2
Bring back static sqlite linking
simolus3 8f5f8cd
Fix linter errors
simolus3 f41b0a4
Fix Swift tests
simolus3 51521d8
Delete proguard rules
simolus3 fd04adc
grdb drivers
stevensJourney b32f7bf
wip: lease all connections
stevensJourney 9008648
revert databasegroup changes.
stevensJourney d6697d9
Merge branch 'main' into grdb-drivers
stevensJourney a92930a
update after merging
stevensJourney ef4160c
revert test change
stevensJourney 068d8ed
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney c0bdde9
improve error handling
stevensJourney 937d452
Use SQLite Session API for Swift updates.
stevensJourney 59408b0
Code cleanup. Fix lint error.
stevensJourney 07267c1
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney 587934c
cleanup APIs for sessions
stevensJourney 5434a5f
Merge remote-tracking branch 'origin/main' into grdb-drivers
stevensJourney 9f855e4
move Swift pool logic
stevensJourney 11add5c
Add changelog entry
stevensJourney File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SwiftSQLiteConnectionPool.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package com.powersync | ||
|
||
import androidx.sqlite.SQLiteStatement | ||
import cnames.structs.sqlite3 | ||
import co.touchlab.kermit.Logger | ||
import com.powersync.db.driver.SQLiteConnectionLease | ||
import com.powersync.db.driver.SQLiteConnectionPool | ||
import com.powersync.db.schema.Schema | ||
import com.powersync.sqlite.Database | ||
import io.ktor.utils.io.CancellationException | ||
import kotlinx.cinterop.CPointer | ||
import kotlinx.cinterop.ExperimentalForeignApi | ||
import kotlinx.coroutines.DelicateCoroutinesApi | ||
import kotlinx.coroutines.GlobalScope | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.SharedFlow | ||
import kotlinx.coroutines.runBlocking | ||
|
||
@OptIn(ExperimentalPowerSyncAPI::class) | ||
internal class RawConnectionLease | ||
@OptIn(ExperimentalForeignApi::class) | ||
constructor( | ||
connectionPointer: CPointer<sqlite3>, | ||
) : SQLiteConnectionLease { | ||
private var isCompleted = false | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
private var db = Database(connectionPointer) | ||
|
||
private fun checkNotCompleted() { | ||
check(!isCompleted) { "Connection lease already closed" } | ||
} | ||
|
||
override suspend fun isInTransaction(): Boolean = isInTransactionSync() | ||
|
||
override fun isInTransactionSync(): Boolean { | ||
checkNotCompleted() | ||
return db.inTransaction() | ||
} | ||
|
||
override suspend fun <R> usePrepared( | ||
sql: String, | ||
block: (SQLiteStatement) -> R, | ||
): R = usePreparedSync(sql, block) | ||
|
||
override fun <R> usePreparedSync( | ||
sql: String, | ||
block: (SQLiteStatement) -> R, | ||
): R { | ||
checkNotCompleted() | ||
return db.prepare(sql).use(block) | ||
} | ||
} | ||
|
||
/** | ||
* We only allow synchronous callbacks on the Swift side for leased READ/WRITE connections. | ||
* We also get a SQLite connection pointer (sqlite3*) from Swift side. which is used in a [Database] | ||
*/ | ||
|
||
public interface SwiftPoolAdapter { | ||
@OptIn(ExperimentalForeignApi::class) | ||
@Throws(PowerSyncException::class, CancellationException::class) | ||
public suspend fun leaseRead(callback: (CPointer<sqlite3>) -> Unit) | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
@Throws(PowerSyncException::class, CancellationException::class) | ||
public suspend fun leaseWrite(callback: (CPointer<sqlite3>) -> Unit) | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
@Throws(PowerSyncException::class, CancellationException::class) | ||
public suspend fun leaseAll(callback: (CPointer<sqlite3>, List<CPointer<sqlite3>>) -> Unit) | ||
|
||
public suspend fun closePool() | ||
} | ||
|
||
@OptIn(ExperimentalPowerSyncAPI::class) | ||
public open class SwiftSQLiteConnectionPool | ||
@OptIn(ExperimentalForeignApi::class) | ||
constructor( | ||
private val adapter: SwiftPoolAdapter, | ||
) : SQLiteConnectionPool { | ||
private val _updates = MutableSharedFlow<Set<String>>(replay = 0) | ||
override val updates: SharedFlow<Set<String>> get() = _updates | ||
|
||
public fun pushUpdate(update: Set<String>) { | ||
_updates.tryEmit(update) | ||
} | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
override suspend fun <T> read(callback: suspend (SQLiteConnectionLease) -> T): T { | ||
var result: T? = null | ||
adapter.leaseRead { | ||
/** | ||
* For GRDB, this should be running inside the callback | ||
* ```swift | ||
* db.write { | ||
* // should be here | ||
* } | ||
* ``` | ||
*/ | ||
val lease = RawConnectionLease(it) | ||
runBlocking { | ||
result = callback(lease) | ||
} | ||
} | ||
return result as T | ||
} | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
override suspend fun <T> write(callback: suspend (SQLiteConnectionLease) -> T): T { | ||
var result: T? = null | ||
adapter.leaseWrite { | ||
val lease = RawConnectionLease(it) | ||
runBlocking { | ||
result = callback(lease) | ||
} | ||
} | ||
return result as T | ||
} | ||
|
||
@OptIn(ExperimentalForeignApi::class) | ||
override suspend fun <R> withAllConnections(action: suspend (SQLiteConnectionLease, List<SQLiteConnectionLease>) -> R) { | ||
adapter.leaseAll { writerPtr, readerPtrs -> | ||
runBlocking { | ||
action(RawConnectionLease(writerPtr), readerPtrs.map { RawConnectionLease(it) }) | ||
} | ||
} | ||
} | ||
|
||
override suspend fun close() { | ||
adapter.closePool() | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalPowerSyncAPI::class, DelicateCoroutinesApi::class) | ||
public fun openPowerSyncWithPool( | ||
pool: SQLiteConnectionPool, | ||
identifier: String, | ||
schema: Schema, | ||
logger: Logger, | ||
): PowerSyncDatabase = | ||
PowerSyncDatabase.opened( | ||
pool = pool, | ||
scope = GlobalScope, | ||
schema = schema, | ||
identifier = identifier, | ||
logger = logger, | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ import kotlinx.cinterop.value | |
* [com.powersync.db.driver.InternalConnectionPool] and called from [kotlinx.coroutines.Dispatchers.IO] | ||
* to make these APIs asynchronous. | ||
*/ | ||
internal class Database( | ||
public class Database( | ||
private val ptr: CPointer<sqlite3>, | ||
) : SQLiteConnection { | ||
override fun inTransaction(): Boolean { | ||
|
@@ -52,22 +52,26 @@ internal class Database( | |
Statement(sql, ptr, stmtPtr.value!!) | ||
} | ||
|
||
fun loadExtension( | ||
public fun loadExtension( | ||
|
||
filename: String, | ||
entrypoint: String, | ||
) = memScoped { | ||
val errorMessagePointer = alloc<CPointerVar<ByteVar>>() | ||
val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr) | ||
|
||
if (resultCode != 0) { | ||
val errorMessage = errorMessagePointer.value?.toKStringFromUtf8() | ||
if (errorMessage != null) { | ||
sqlite3_free(errorMessagePointer.value) | ||
} | ||
): Unit = | ||
memScoped { | ||
val errorMessagePointer = alloc<CPointerVar<ByteVar>>() | ||
val resultCode = sqlite3_load_extension(ptr, filename, entrypoint, errorMessagePointer.ptr) | ||
|
||
if (resultCode != 0) { | ||
val errorMessage = errorMessagePointer.value?.toKStringFromUtf8() | ||
if (errorMessage != null) { | ||
sqlite3_free(errorMessagePointer.value) | ||
} | ||
|
||
throw PowerSyncException("Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}", null) | ||
throw PowerSyncException( | ||
"Could not load extension ($resultCode): ${errorMessage ?: "unknown error"}", | ||
null, | ||
) | ||
} | ||
} | ||
} | ||
|
||
override fun close() { | ||
sqlite3_close_v2(ptr) | ||
|
@@ -79,7 +83,7 @@ internal class Database( | |
} | ||
} | ||
|
||
companion object { | ||
internal companion object { | ||
fun open( | ||
path: String, | ||
flags: Int, | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it looks like we wouldn't extend this class from Swift, I don't think it would have to be
open
?