Skip to content

Commit 9884eb0

Browse files
committed
Fix leaking statements
1 parent cbe1691 commit 9884eb0

File tree

20 files changed

+177
-316
lines changed

20 files changed

+177
-316
lines changed

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ kotlin {
234234

235235
appleMain.dependencies {
236236
implementation(libs.ktor.client.darwin)
237+
implementation(projects.staticSqliteDriver)
237238
}
238239

239240
commonTest.dependencies {

core/src/androidMain/kotlin/com/powersync/DatabaseDriverFactory.android.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.powersync
33
import android.content.Context
44
import androidx.sqlite.SQLiteConnection
55
import com.powersync.db.loadExtensions
6-
import com.powersync.db.setSchemaVersion
76
import com.powersync.internal.driver.AndroidDriver
87
import com.powersync.internal.driver.ConnectionListener
98
import com.powersync.internal.driver.JdbcConnection
@@ -27,7 +26,6 @@ public actual class DatabaseDriverFactory(
2726

2827
val driver = AndroidDriver(context)
2928
val connection = driver.openDatabase(dbPath, readOnly, listener) as JdbcConnection
30-
connection.setSchemaVersion()
3129
connection.loadExtensions(
3230
"libpowersync.so" to "sqlite3_powersync_init",
3331
)
Lines changed: 43 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,181 +1,46 @@
11
package com.powersync
22

3-
import app.cash.sqldelight.db.QueryResult
4-
import co.touchlab.sqliter.DatabaseConfiguration
5-
import co.touchlab.sqliter.DatabaseConfiguration.Logging
6-
import co.touchlab.sqliter.DatabaseConnection
7-
import co.touchlab.sqliter.NO_VERSION_CHECK
8-
import co.touchlab.sqliter.interop.Logger
9-
import co.touchlab.sqliter.interop.SqliteErrorType
10-
import co.touchlab.sqliter.sqlite3.sqlite3_commit_hook
11-
import co.touchlab.sqliter.sqlite3.sqlite3_enable_load_extension
12-
import co.touchlab.sqliter.sqlite3.sqlite3_load_extension
13-
import co.touchlab.sqliter.sqlite3.sqlite3_rollback_hook
14-
import co.touchlab.sqliter.sqlite3.sqlite3_update_hook
3+
import androidx.sqlite.SQLiteConnection
154
import com.powersync.DatabaseDriverFactory.Companion.powerSyncExtensionPath
16-
import com.powersync.db.internal.InternalSchema
17-
import com.powersync.persistence.driver.NativeSqliteDriver
18-
import com.powersync.persistence.driver.wrapConnection
5+
import com.powersync.internal.driver.ConnectionListener
6+
import com.powersync.internal.driver.NativeConnection
7+
import com.powersync.internal.driver.NativeDriver
198
import kotlinx.cinterop.ByteVar
209
import kotlinx.cinterop.CPointerVar
2110
import kotlinx.cinterop.ExperimentalForeignApi
2211
import kotlinx.cinterop.MemScope
23-
import kotlinx.cinterop.StableRef
12+
import kotlinx.cinterop.UnsafeNumber
2413
import kotlinx.cinterop.alloc
25-
import kotlinx.cinterop.asStableRef
2614
import kotlinx.cinterop.free
2715
import kotlinx.cinterop.nativeHeap
2816
import kotlinx.cinterop.ptr
29-
import kotlinx.cinterop.staticCFunction
3017
import kotlinx.cinterop.toKString
3118
import kotlinx.cinterop.value
32-
import kotlinx.coroutines.CoroutineScope
19+
import kotlinx.io.files.Path
20+
import platform.Foundation.NSApplicationSupportDirectory
3321
import platform.Foundation.NSBundle
22+
import platform.Foundation.NSFileManager
23+
import platform.Foundation.NSSearchPathForDirectoriesInDomains
24+
import platform.Foundation.NSUserDomainMask
25+
import sqlite3.SQLITE_OK
26+
import sqlite3.sqlite3_enable_load_extension
27+
import sqlite3.sqlite3_load_extension
3428

3529
@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING")
3630
@OptIn(ExperimentalForeignApi::class)
3731
public actual class DatabaseDriverFactory {
38-
internal actual fun createDriver(
39-
scope: CoroutineScope,
32+
internal actual fun openDatabase(
4033
dbFilename: String,
4134
dbDirectory: String?,
4235
readOnly: Boolean,
43-
): PsSqlDriver {
44-
val schema = InternalSchema
45-
val sqlLogger =
46-
object : Logger {
47-
override val eActive: Boolean
48-
get() = false
49-
override val vActive: Boolean
50-
get() = false
51-
52-
override fun eWrite(
53-
message: String,
54-
exception: Throwable?,
55-
) {
56-
}
57-
58-
override fun trace(message: String) {}
59-
60-
override fun vWrite(message: String) {}
61-
}
62-
63-
// Create a deferred driver reference for hook registrations
64-
// This must exist before we create the driver since we require
65-
// a pointer for C hooks
66-
val deferredDriver = DeferredDriver()
67-
68-
val driver =
69-
PsSqlDriver(
70-
driver =
71-
NativeSqliteDriver(
72-
configuration =
73-
DatabaseConfiguration(
74-
name = dbFilename,
75-
version =
76-
if (!readOnly) {
77-
schema.version.toInt()
78-
} else {
79-
// Don't do migrations on read only connections
80-
NO_VERSION_CHECK
81-
},
82-
create = { connection ->
83-
wrapConnection(connection) {
84-
schema.create(
85-
it,
86-
)
87-
}
88-
},
89-
loggingConfig = Logging(logger = sqlLogger),
90-
lifecycleConfig =
91-
DatabaseConfiguration.Lifecycle(
92-
onCreateConnection = { connection ->
93-
setupSqliteBinding(connection, deferredDriver)
94-
wrapConnection(connection) { driver ->
95-
schema.create(driver)
96-
}
97-
},
98-
onCloseConnection = { connection ->
99-
deregisterSqliteBinding(connection)
100-
},
101-
),
102-
),
103-
),
104-
)
105-
106-
// The iOS driver implementation generates 1 write and 1 read connection internally
107-
// It uses the read connection for all queries and the write connection for all
108-
// execute statements. Unfortunately the driver does not seem to respond to query
109-
// calls if the read connection count is set to zero.
110-
// We'd like to ensure a driver is set to read-only. Ideally we could do this in the
111-
// onCreateConnection lifecycle hook, but this runs before driver internal migrations.
112-
// Setting the connection to read only there breaks migrations.
113-
// We explicitly execute this pragma to reflect and guard the "write" connection.
114-
// The read connection already has this set.
115-
if (readOnly) {
116-
driver.execute("PRAGMA query_only=true")
117-
}
118-
119-
// Ensure internal read pool has created a connection at this point. This makes connection
120-
// initialization a bit more deterministic.
121-
driver.executeQuery(
122-
identifier = null,
123-
sql = "SELECT 1",
124-
mapper = { QueryResult.Value(it.getLong(0)) },
125-
parameters = 0,
126-
)
127-
128-
deferredDriver.setDriver(driver)
129-
130-
return driver
131-
}
132-
133-
private fun setupSqliteBinding(
134-
connection: DatabaseConnection,
135-
driver: DeferredDriver,
136-
) {
137-
connection.loadPowerSyncSqliteCoreExtension()
138-
139-
val ptr = connection.getDbPointer().getPointer(MemScope())
140-
val driverRef = StableRef.create(driver)
141-
142-
sqlite3_update_hook(
143-
ptr,
144-
staticCFunction { usrPtr, updateType, dbName, tableName, rowId ->
145-
usrPtr!!
146-
.asStableRef<DeferredDriver>()
147-
.get()
148-
.updateTableHook(tableName!!.toKString())
149-
},
150-
driverRef.asCPointer(),
151-
)
152-
153-
sqlite3_commit_hook(
154-
ptr,
155-
staticCFunction { usrPtr ->
156-
usrPtr!!.asStableRef<DeferredDriver>().get().onTransactionCommit(true)
157-
0
158-
},
159-
driverRef.asCPointer(),
160-
)
161-
162-
sqlite3_rollback_hook(
163-
ptr,
164-
staticCFunction { usrPtr ->
165-
usrPtr!!.asStableRef<DeferredDriver>().get().onTransactionCommit(false)
166-
},
167-
driverRef.asCPointer(),
168-
)
169-
}
170-
171-
private fun deregisterSqliteBinding(connection: DatabaseConnection) {
172-
val basePtr = connection.getDbPointer().getPointer(MemScope())
173-
174-
sqlite3_update_hook(
175-
basePtr,
176-
null,
177-
null,
178-
)
36+
listener: ConnectionListener?
37+
): SQLiteConnection {
38+
val directory = dbDirectory ?: defaultDatabaseDirectory()
39+
val path = Path(directory, dbFilename).toString()
40+
val db = NativeDriver().openNativeDatabase(path, readOnly, listener)
41+
42+
db.loadPowerSyncSqliteCoreExtension()
43+
return db
17944
}
18045

18146
internal companion object {
@@ -192,18 +57,34 @@ public actual class DatabaseDriverFactory {
19257
// Construct full path to the shared library inside the bundle
19358
bundlePath.let { "$it/powersync-sqlite-core" }
19459
}
60+
61+
@OptIn(UnsafeNumber::class)
62+
private fun defaultDatabaseDirectory(search: String = "databases"): String {
63+
// This needs to be compatible with https://github.com/touchlab/SQLiter/blob/a37bbe7e9c65e6a5a94c5bfcaccdaae55ad2bac9/sqliter-driver/src/appleMain/kotlin/co/touchlab/sqliter/DatabaseFileContext.kt#L36-L51
64+
val paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true);
65+
val documentsDirectory = paths[0] as String;
66+
67+
val databaseDirectory = "$documentsDirectory/$search"
68+
69+
val fileManager = NSFileManager.defaultManager()
70+
71+
if (!fileManager.fileExistsAtPath(databaseDirectory))
72+
fileManager.createDirectoryAtPath(databaseDirectory, true, null, null); //Create folder
73+
74+
return databaseDirectory
75+
}
19576
}
19677
}
19778

198-
internal fun DatabaseConnection.loadPowerSyncSqliteCoreExtensionDynamically() {
199-
val ptr = getDbPointer().getPointer(MemScope())
79+
internal fun NativeConnection.loadPowerSyncSqliteCoreExtensionDynamically() {
80+
val ptr = sqlite.getPointer(MemScope())
20081
val extensionPath = powerSyncExtensionPath
20182

20283
// Enable extension loading
20384
// We don't disable this after the fact, this should allow users to load their own extensions
20485
// in future.
20586
val enableResult = sqlite3_enable_load_extension(ptr, 1)
206-
if (enableResult != SqliteErrorType.SQLITE_OK.code) {
87+
if (enableResult != SQLITE_OK) {
20788
throw PowerSyncException(
20889
"Could not dynamically load the PowerSync SQLite core extension",
20990
cause =
@@ -219,7 +100,7 @@ internal fun DatabaseConnection.loadPowerSyncSqliteCoreExtensionDynamically() {
219100
sqlite3_load_extension(ptr, extensionPath, "sqlite3_powersync_init", errMsg.ptr)
220101
val resultingError = errMsg.value
221102
nativeHeap.free(errMsg)
222-
if (result != SqliteErrorType.SQLITE_OK.code) {
103+
if (result != SQLITE_OK) {
223104
val errorMessage = resultingError?.toKString() ?: "Unknown error"
224105
throw PowerSyncException(
225106
"Could not load the PowerSync SQLite core extension",
@@ -231,4 +112,4 @@ internal fun DatabaseConnection.loadPowerSyncSqliteCoreExtensionDynamically() {
231112
}
232113
}
233114

234-
internal expect fun DatabaseConnection.loadPowerSyncSqliteCoreExtension()
115+
internal expect fun NativeConnection.loadPowerSyncSqliteCoreExtension()

core/src/appleMain/kotlin/com/powersync/DeferredDriver.kt

Lines changed: 0 additions & 27 deletions
This file was deleted.

core/src/commonIntegrationTest/kotlin/com/powersync/sync/SyncIntegrationTest.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,10 @@ abstract class BaseSyncIntegrationTest(
597597
val turbine = database.currentStatus.asFlow().testIn(scope)
598598
turbine.waitFor { it.connected }
599599

600-
val query = database.watch("SELECT name FROM users") { it.getString(0)!! }.testIn(scope)
600+
val query = database.watch("SELECT name FROM users") {
601+
println("interpreting results: ${it.getString(0)}")
602+
it.getString(0)!!
603+
}.testIn(scope)
601604
query.awaitItem() shouldBe listOf("local write")
602605

603606
syncLines.send(SyncLine.KeepAlive(tokenExpiresIn = 1234))
Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.powersync.db
22

3-
import androidx.sqlite.execSQL
43
import com.powersync.internal.driver.JdbcConnection
54

65
internal fun JdbcConnection.loadExtensions(vararg extensions: Pair<String, String>) {
@@ -16,11 +15,3 @@ internal fun JdbcConnection.loadExtensions(vararg extensions: Pair<String, Strin
1615
}
1716
connection.database.enable_load_extension(false)
1817
}
19-
20-
/**
21-
* Sets the user version pragma to `1` to continue the behavior of older versions of the PowerSync
22-
* SDK.
23-
*/
24-
internal fun JdbcConnection.setSchemaVersion() {
25-
execSQL("pragma user_version = 1")
26-
}

core/src/commonMain/kotlin/com/powersync/db/PowerSyncDatabaseImpl.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import kotlinx.datetime.Instant
4646
import kotlinx.datetime.LocalDateTime
4747
import kotlinx.datetime.TimeZone
4848
import kotlinx.datetime.toInstant
49+
import kotlin.math.log
4950
import kotlin.time.Duration.Companion.milliseconds
5051

5152
/**
@@ -90,6 +91,7 @@ internal class PowerSyncDatabaseImpl(
9091
dbFilename = dbFilename,
9192
dbDirectory = dbDirectory,
9293
writeLockMutex = resource.group.writeLockMutex,
94+
logger = logger,
9395
)
9496

9597
internal val bucketStorage: BucketStorage = BucketStorageImpl(internalDb, logger)

0 commit comments

Comments
 (0)