diff --git a/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt new file mode 100644 index 00000000..357d404b --- /dev/null +++ b/common/src/nativeMain/kotlin/com/powersync/db/NativeConnectionFactory.kt @@ -0,0 +1,37 @@ +package com.powersync.db + +import androidx.sqlite.SQLiteConnection +import com.powersync.ExperimentalPowerSyncAPI +import com.powersync.PersistentConnectionFactory +import com.powersync.PowerSyncException +import com.powersync.resolvePowerSyncLoadableExtensionPath +import com.powersync.sqlite.Database + +/** + * A [PersistentConnectionFactory] implementation delegating to static `sqlite3_` invocations through cinterop. + */ +public abstract class NativeConnectionFactory : PersistentConnectionFactory { + @OptIn(ExperimentalPowerSyncAPI::class) + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection { + // On some platforms, most notably watchOS, there's no dynamic extension loading and the core extension is + // registered via sqlite3_auto_extension. + val extensionPath = resolvePowerSyncLoadableExtensionPath() + val db = Database.open(path, openFlags) + + if (extensionPath != null) { + try { + db.loadExtension(extensionPath, "sqlite3_powersync_init") + } catch (e: PowerSyncException) { + db.close() + throw e + } + } + + return db + } + + override fun openInMemoryConnection(): SQLiteConnection = openConnection(":memory:", 0x02) +} diff --git a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt index bc34bfd5..b8a6e035 100644 --- a/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt +++ b/core/src/nativeMain/kotlin/com/powersync/DatabaseDriverFactory.native.kt @@ -1,35 +1,10 @@ package com.powersync -import androidx.sqlite.SQLiteConnection -import com.powersync.sqlite.Database +import com.powersync.db.NativeConnectionFactory @Suppress(names = ["EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING"]) -public actual class DatabaseDriverFactory : PersistentConnectionFactory { +public actual class DatabaseDriverFactory : NativeConnectionFactory() { actual override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) - - @OptIn(ExperimentalPowerSyncAPI::class) - actual override fun openConnection( - path: String, - openFlags: Int, - ): SQLiteConnection { - // On some platforms, most notably watchOS, there's no dynamic extension loading and the core extension is - // registered via sqlite3_auto_extension. - val extensionPath = resolvePowerSyncLoadableExtensionPath() - - val db = Database.open(path, openFlags) - extensionPath?.let { path -> - try { - db.loadExtension(path, "sqlite3_powersync_init") - } catch (e: PowerSyncException) { - db.close() - throw e - } - } - - return db - } - - actual override fun openInMemoryConnection(): SQLiteConnection = openConnection(":memory:", 0x02) } internal actual val inMemoryDriver: InMemoryConnectionFactory = DatabaseDriverFactory() diff --git a/internal/PowerSyncKotlin/build.gradle.kts b/internal/PowerSyncKotlin/build.gradle.kts index 3fa8d56d..319e4b8c 100644 --- a/internal/PowerSyncKotlin/build.gradle.kts +++ b/internal/PowerSyncKotlin/build.gradle.kts @@ -25,7 +25,7 @@ kotlin { baseName = "PowerSyncKotlin" xcf.add(this) - export(project(":core")) + export(projects.common) isStatic = true binaryOption("bundleId", "PowerSyncKotlin") @@ -45,8 +45,9 @@ kotlin { sourceSets { commonMain.dependencies { - api(project(":core")) + api(projects.common) implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.darwin) } all { diff --git a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt index 81524368..39382e2a 100644 --- a/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt +++ b/internal/PowerSyncKotlin/src/appleMain/kotlin/com/powersync/SDK.kt @@ -2,6 +2,9 @@ package com.powersync +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.execSQL +import com.powersync.db.NativeConnectionFactory import com.powersync.db.crud.CrudTransaction import com.powersync.sync.SyncClientConfiguration import com.powersync.sync.SyncOptions @@ -12,6 +15,30 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import io.ktor.client.plugins.logging.Logger as KtorLogger +public fun sqlite3DatabaseFactory(initialStatements: List): PersistentConnectionFactory { + @OptIn(ExperimentalPowerSyncAPI::class) + return object : NativeConnectionFactory() { + override fun resolveDefaultDatabasePath(dbFilename: String): String = appleDefaultDatabasePath(dbFilename) + + override fun openConnection( + path: String, + openFlags: Int, + ): SQLiteConnection { + val conn = super.openConnection(path, openFlags) + try { + for (statement in initialStatements) { + conn.execSQL(statement) + } + } catch (e: PowerSyncException) { + conn.close() + throw e + } + + return super.openConnection(path, openFlags) + } + } +} + /** * Helper class designed to bridge SKIEE methods and allow them to throw * `PowerSyncException`. This is necessary because these exceptions cannot