From 245a526b07aec27749701b26a7c19fc352e407b2 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 23:33:01 +0100 Subject: [PATCH 1/3] feat: [DATABASE] Room 3 query logs through a logging SQLite driver --- .../room3/FloconLoggingSQLiteDriver.kt | 5 ++ FloconAndroid/database/room3/build.gradle.kts | 4 + .../room3/FloconLoggingSQLiteDriver.nonWeb.kt | 84 +++++++++++++++++++ .../flocon/database/room3/FloconLogging.kt | 20 +++++ .../room3/FloconLoggingSQLiteDriver.wasmJs.kt | 84 +++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 FloconAndroid/database/room3-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.kt create mode 100644 FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt create mode 100644 FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLogging.kt create mode 100644 FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt diff --git a/FloconAndroid/database/room3-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.kt b/FloconAndroid/database/room3-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.kt new file mode 100644 index 000000000..1489b2512 --- /dev/null +++ b/FloconAndroid/database/room3-no-op/src/commonMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.kt @@ -0,0 +1,5 @@ +package io.github.openflocon.flocon.database.room3 + +import androidx.sqlite.SQLiteDriver + +fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = this // no op diff --git a/FloconAndroid/database/room3/build.gradle.kts b/FloconAndroid/database/room3/build.gradle.kts index 47ea8cb61..768bb9b0c 100644 --- a/FloconAndroid/database/room3/build.gradle.kts +++ b/FloconAndroid/database/room3/build.gradle.kts @@ -13,6 +13,10 @@ kotlin { } sourceSets { + // the SQLite driver methods are not suspend on these targets, they are on the web + listOf("androidMain", "jvmMain", "iosMain").forEach { + named(it) { kotlin.srcDir("src/nonWebMain/kotlin") } + } commonMain { kotlin.srcDir("src/roomMain/kotlin") dependencies { diff --git a/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt b/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt new file mode 100644 index 000000000..dcea620cd --- /dev/null +++ b/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt @@ -0,0 +1,84 @@ +package io.github.openflocon.flocon.database.room3 + +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.SQLiteStatement +import io.github.openflocon.flocon.Flocon +import io.github.openflocon.flocon.database.core.databasePlugin + +actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id) + +internal class FloconLoggingSQLiteDriver( + private val delegate: SQLiteDriver, + private val dbId: String, +) : SQLiteDriver { + + override val hasConnectionPool: Boolean + get() = delegate.hasConnectionPool + + override fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId) +} + +private class FloconLoggingConnection( + private val delegate: SQLiteConnection, + private val dbId: String, +) : SQLiteConnection by delegate { + + override fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql) +} + +/** + * Logs the query when it starts running: the first `step`, and the first one after each `reset`. + */ +private class FloconLoggingStatement( + private val delegate: SQLiteStatement, + private val dbId: String, + private val sql: String, +) : SQLiteStatement by delegate { + + private val bindArgs = mutableMapOf() + private var logged = false + + override fun bindBlob(index: Int, value: ByteArray) { + bindArgs[index] = "[blob ${value.size} bytes]" + delegate.bindBlob(index, value) + } + + override fun bindDouble(index: Int, value: Double) { + bindArgs[index] = value + delegate.bindDouble(index, value) + } + + override fun bindLong(index: Int, value: Long) { + bindArgs[index] = value + delegate.bindLong(index, value) + } + + override fun bindText(index: Int, value: String) { + bindArgs[index] = value + delegate.bindText(index, value) + } + + override fun bindNull(index: Int) { + bindArgs[index] = null + delegate.bindNull(index) + } + + override fun step(): Boolean { + if (!logged) { + logged = true + Flocon.databasePlugin.logQuery(dbId = dbId, sqlQuery = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) + } + return delegate.step() + } + + override fun reset() { + logged = false + delegate.reset() + } + + override fun clearBindings() { + bindArgs.clear() + delegate.clearBindings() + } +} diff --git a/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLogging.kt b/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLogging.kt new file mode 100644 index 000000000..1a9bedccf --- /dev/null +++ b/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLogging.kt @@ -0,0 +1,20 @@ +package io.github.openflocon.flocon.database.room3 + +import androidx.sqlite.SQLiteDriver + +/** + * Shows the queries run by a Room 3 database in the desktop app: Room 3 has no query callback, so the + * driver of the database is wrapped. + * + * ```kotlin + * Room.databaseBuilder(context, "dogs_database") + * .setDriver(BundledSQLiteDriver().floconLogs(id = "dogs")) + * .build() + * .also { Flocon.databasePlugin.register(id = "dogs", displayName = "Dogs", database = it) } + * ``` + * + * Meant for the drivers that run the queries themselves (bundled, framework, native), not for the web worker driver. + * + * @param id the id the database is registered with, see [register] + */ +expect fun SQLiteDriver.floconLogs(id: String): SQLiteDriver diff --git a/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt b/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt new file mode 100644 index 000000000..a9c083c4d --- /dev/null +++ b/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt @@ -0,0 +1,84 @@ +package io.github.openflocon.flocon.database.room3 + +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.SQLiteStatement +import io.github.openflocon.flocon.Flocon +import io.github.openflocon.flocon.database.core.databasePlugin + +actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id) + +internal class FloconLoggingSQLiteDriver( + private val delegate: SQLiteDriver, + private val dbId: String, +) : SQLiteDriver { + + override val hasConnectionPool: Boolean + get() = delegate.hasConnectionPool + + override suspend fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId) +} + +private class FloconLoggingConnection( + private val delegate: SQLiteConnection, + private val dbId: String, +) : SQLiteConnection by delegate { + + override suspend fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql) +} + +/** + * Logs the query when it starts running: the first `step`, and the first one after each `reset`. + */ +private class FloconLoggingStatement( + private val delegate: SQLiteStatement, + private val dbId: String, + private val sql: String, +) : SQLiteStatement by delegate { + + private val bindArgs = mutableMapOf() + private var logged = false + + override fun bindBlob(index: Int, value: ByteArray) { + bindArgs[index] = "[blob ${value.size} bytes]" + delegate.bindBlob(index, value) + } + + override fun bindDouble(index: Int, value: Double) { + bindArgs[index] = value + delegate.bindDouble(index, value) + } + + override fun bindLong(index: Int, value: Long) { + bindArgs[index] = value + delegate.bindLong(index, value) + } + + override fun bindText(index: Int, value: String) { + bindArgs[index] = value + delegate.bindText(index, value) + } + + override fun bindNull(index: Int) { + bindArgs[index] = null + delegate.bindNull(index) + } + + override suspend fun step(): Boolean { + if (!logged) { + logged = true + Flocon.databasePlugin.logQuery(dbId = dbId, sqlQuery = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) + } + return delegate.step() + } + + override fun reset() { + logged = false + delegate.reset() + } + + override fun clearBindings() { + bindArgs.clear() + delegate.clearBindings() + } +} From 160f89ad641e1afdfeaa3e0f56eeb86a5a3bdd31 Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 23:39:27 +0100 Subject: [PATCH 2/3] feat: [DATABASE] Log the queries of the Room 3 samples, test the logging driver, document it --- .../room3/FloconLoggingSQLiteDriverTest.kt | 95 +++++++++++++++++++ .../room3/FloconLoggingSQLiteDriver.nonWeb.kt | 13 +-- .../flocon/database/room3/QueryLogger.kt | 14 +++ .../room3/FloconLoggingSQLiteDriver.wasmJs.kt | 13 +-- .../flocon/myapplication/multi/Databases.kt | 11 ++- .../kotlin/com/flocon/sample/desktop/Main.kt | 5 +- .../kotlin/com/flocon/sample/ios/Databases.kt | 5 +- .../com/flocon/sample/wasm/Databases.kt | 5 +- docs2/database.md | 14 ++- 9 files changed, 154 insertions(+), 21 deletions(-) create mode 100644 FloconAndroid/database/room3/src/jvmTest/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriverTest.kt create mode 100644 FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/QueryLogger.kt diff --git a/FloconAndroid/database/room3/src/jvmTest/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriverTest.kt b/FloconAndroid/database/room3/src/jvmTest/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriverTest.kt new file mode 100644 index 000000000..23d10b0cd --- /dev/null +++ b/FloconAndroid/database/room3/src/jvmTest/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriverTest.kt @@ -0,0 +1,95 @@ +package io.github.openflocon.flocon.database.room3 + +import androidx.sqlite.SQLiteConnection +import androidx.sqlite.SQLiteDriver +import androidx.sqlite.SQLiteStatement +import kotlin.test.Test +import kotlin.test.assertEquals + +class FloconLoggingSQLiteDriverTest { + + private data class Log(val dbId: String, val sql: String, val bindArgs: List) + + private val logs = mutableListOf() + private val statement = FakeStatement() + private val driver = FloconLoggingSQLiteDriver( + delegate = object : SQLiteDriver { + override fun open(fileName: String): SQLiteConnection = object : SQLiteConnection { + override fun prepare(sql: String): SQLiteStatement = statement + override fun close() = Unit + } + }, + dbId = "dogs", + logger = { dbId, sql, bindArgs -> logs += Log(dbId, sql, bindArgs) }, + ) + + private fun prepare(sql: String) = driver.open("dogs.db").prepare(sql) + + @Test + fun `the query is logged with its id and its arguments when it runs`() { + val prepared = prepare("INSERT INTO dog (name, age) VALUES (?, ?)") + prepared.bindText(1, "Rex") + prepared.bindLong(2, 4) + assertEquals(emptyList(), logs) + + prepared.step() + + assertEquals(listOf(Log("dogs", "INSERT INTO dog (name, age) VALUES (?, ?)", listOf("Rex", 4L))), logs) + } + + @Test + fun `the arguments are in the order of their index`() { + val prepared = prepare("SELECT * FROM dog WHERE age > ? AND name = ?") + prepared.bindText(2, "Rex") + prepared.bindNull(1) + + prepared.step() + + assertEquals(listOf(null, "Rex"), logs.single().bindArgs) + } + + @Test + fun `a query is logged once per run, not once per row`() { + val prepared = prepare("SELECT * FROM dog") + + prepared.step() + prepared.step() + prepared.step() + assertEquals(1, logs.size) + + prepared.reset() + prepared.step() + assertEquals(2, logs.size) + } + + @Test + fun `the statement still runs on the wrapped driver`() { + val prepared = prepare("SELECT 1") + prepared.bindLong(1, 1) + + prepared.step() + + assertEquals(listOf("bindLong 1", "step"), statement.calls) + } + + private class FakeStatement : SQLiteStatement { + val calls = mutableListOf() + override fun bindBlob(index: Int, value: ByteArray) { calls += "bindBlob $index" } + override fun bindDouble(index: Int, value: Double) { calls += "bindDouble $index" } + override fun bindLong(index: Int, value: Long) { calls += "bindLong $index" } + override fun bindText(index: Int, value: String) { calls += "bindText $index" } + override fun bindNull(index: Int) { calls += "bindNull $index" } + override fun getBlob(index: Int): ByteArray = ByteArray(0) + override fun getDouble(index: Int): Double = 0.0 + override fun getLong(index: Int): Long = 0 + override fun getText(index: Int): String = "" + override fun isNull(index: Int): Boolean = false + override fun getColumnCount(): Int = 0 + override fun getColumnName(index: Int): String = "" + override fun getColumnType(index: Int): Int = 0 + override fun step(): Boolean { calls += "step"; return false } + override fun reset() { calls += "reset" } + override fun clearBindings() { calls += "clearBindings" } + override fun close() = Unit + } +} diff --git a/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt b/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt index dcea620cd..7b1a8c31b 100644 --- a/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt +++ b/FloconAndroid/database/room3/src/nonWebMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.nonWeb.kt @@ -3,28 +3,28 @@ package io.github.openflocon.flocon.database.room3 import androidx.sqlite.SQLiteConnection import androidx.sqlite.SQLiteDriver import androidx.sqlite.SQLiteStatement -import io.github.openflocon.flocon.Flocon -import io.github.openflocon.flocon.database.core.databasePlugin -actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id) +actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id, logger = DatabasePluginQueryLogger) internal class FloconLoggingSQLiteDriver( private val delegate: SQLiteDriver, private val dbId: String, + private val logger: QueryLogger, ) : SQLiteDriver { override val hasConnectionPool: Boolean get() = delegate.hasConnectionPool - override fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId) + override fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId, logger) } private class FloconLoggingConnection( private val delegate: SQLiteConnection, private val dbId: String, + private val logger: QueryLogger, ) : SQLiteConnection by delegate { - override fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql) + override fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql, logger) } /** @@ -34,6 +34,7 @@ private class FloconLoggingStatement( private val delegate: SQLiteStatement, private val dbId: String, private val sql: String, + private val logger: QueryLogger, ) : SQLiteStatement by delegate { private val bindArgs = mutableMapOf() @@ -67,7 +68,7 @@ private class FloconLoggingStatement( override fun step(): Boolean { if (!logged) { logged = true - Flocon.databasePlugin.logQuery(dbId = dbId, sqlQuery = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) + logger.log(dbId = dbId, sql = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) } return delegate.step() } diff --git a/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/QueryLogger.kt b/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/QueryLogger.kt new file mode 100644 index 000000000..1da5ac7ac --- /dev/null +++ b/FloconAndroid/database/room3/src/roomMain/kotlin/io/github/openflocon/flocon/database/room3/QueryLogger.kt @@ -0,0 +1,14 @@ +package io.github.openflocon.flocon.database.room3 + +import io.github.openflocon.flocon.Flocon +import io.github.openflocon.flocon.database.core.databasePlugin + +internal fun interface QueryLogger { + fun log(dbId: String, sql: String, bindArgs: List) +} + +internal object DatabasePluginQueryLogger : QueryLogger { + override fun log(dbId: String, sql: String, bindArgs: List) { + Flocon.databasePlugin.logQuery(dbId = dbId, sqlQuery = sql, bindArgs = bindArgs) + } +} diff --git a/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt b/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt index a9c083c4d..6a32b5804 100644 --- a/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt +++ b/FloconAndroid/database/room3/src/wasmJsMain/kotlin/io/github/openflocon/flocon/database/room3/FloconLoggingSQLiteDriver.wasmJs.kt @@ -3,28 +3,28 @@ package io.github.openflocon.flocon.database.room3 import androidx.sqlite.SQLiteConnection import androidx.sqlite.SQLiteDriver import androidx.sqlite.SQLiteStatement -import io.github.openflocon.flocon.Flocon -import io.github.openflocon.flocon.database.core.databasePlugin -actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id) +actual fun SQLiteDriver.floconLogs(id: String): SQLiteDriver = FloconLoggingSQLiteDriver(delegate = this, dbId = id, logger = DatabasePluginQueryLogger) internal class FloconLoggingSQLiteDriver( private val delegate: SQLiteDriver, private val dbId: String, + private val logger: QueryLogger, ) : SQLiteDriver { override val hasConnectionPool: Boolean get() = delegate.hasConnectionPool - override suspend fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId) + override suspend fun open(fileName: String): SQLiteConnection = FloconLoggingConnection(delegate.open(fileName), dbId, logger) } private class FloconLoggingConnection( private val delegate: SQLiteConnection, private val dbId: String, + private val logger: QueryLogger, ) : SQLiteConnection by delegate { - override suspend fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql) + override suspend fun prepare(sql: String): SQLiteStatement = FloconLoggingStatement(delegate.prepare(sql), dbId, sql, logger) } /** @@ -34,6 +34,7 @@ private class FloconLoggingStatement( private val delegate: SQLiteStatement, private val dbId: String, private val sql: String, + private val logger: QueryLogger, ) : SQLiteStatement by delegate { private val bindArgs = mutableMapOf() @@ -67,7 +68,7 @@ private class FloconLoggingStatement( override suspend fun step(): Boolean { if (!logged) { logged = true - Flocon.databasePlugin.logQuery(dbId = dbId, sqlQuery = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) + logger.log(dbId = dbId, sql = sql, bindArgs = bindArgs.entries.sortedBy { it.key }.map { it.value }) } return delegate.step() } diff --git a/FloconAndroid/sample-multiplatform/src/androidMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Databases.kt b/FloconAndroid/sample-multiplatform/src/androidMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Databases.kt index 06b5572e8..5bed509d3 100644 --- a/FloconAndroid/sample-multiplatform/src/androidMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Databases.kt +++ b/FloconAndroid/sample-multiplatform/src/androidMain/kotlin/io/github/openflocon/flocon/myapplication/multi/Databases.kt @@ -2,6 +2,8 @@ package io.github.openflocon.flocon.myapplication.multi import android.content.Context import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver +import io.github.openflocon.flocon.database.room3.floconLogs import io.github.openflocon.flocon.Flocon import io.github.openflocon.flocon.database.core.databasePlugin import io.github.openflocon.flocon.database.room3.register @@ -19,6 +21,7 @@ object Databases { klass = DogDatabase::class.java, name = "dogs_database" ) + .setDriver(AndroidSQLiteDriver().floconLogs(id = "dogs_room3")) .fallbackToDestructiveMigration(dropAllTables = true) .build() dogDatabase = instance @@ -39,7 +42,9 @@ object Databases { context = context.applicationContext, klass = FoodDatabase::class.java, name = "food_database" - ).build() + ) + .setDriver(AndroidSQLiteDriver().floconLogs(id = "food_room3")) + .build() foodDatabase = instance Flocon.databasePlugin.register( id = "food_room3", @@ -58,7 +63,9 @@ object Databases { val instance = Room.inMemoryDatabaseBuilder( context.applicationContext, DogDatabase::class.java, - ).build() + ) + .setDriver(AndroidSQLiteDriver().floconLogs(id = "dogs_room3_memory")) + .build() inMemoryDogDatabase = instance Flocon.databasePlugin.register( id = "dogs_room3_memory", diff --git a/FloconAndroid/sample/desktop/src/jvmMain/kotlin/com/flocon/sample/desktop/Main.kt b/FloconAndroid/sample/desktop/src/jvmMain/kotlin/com/flocon/sample/desktop/Main.kt index 64ac13145..e36c7711d 100644 --- a/FloconAndroid/sample/desktop/src/jvmMain/kotlin/com/flocon/sample/desktop/Main.kt +++ b/FloconAndroid/sample/desktop/src/jvmMain/kotlin/com/flocon/sample/desktop/Main.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.room3.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import io.github.openflocon.flocon.database.room3.floconLogs import coil3.ImageLoader import coil3.PlatformContext import coil3.SingletonImageLoader @@ -108,7 +109,7 @@ private fun dogDatabase(): DogDatabase { val dbFile = File(System.getProperty("java.io.tmpdir"), "flocon_dogs_database.db") return Room.databaseBuilder(name = dbFile.absolutePath) .fallbackToDestructiveMigration(dropAllTables = true) - .setDriver(BundledSQLiteDriver()) + .setDriver(BundledSQLiteDriver().floconLogs(id = "dogs_jvm")) .setQueryCoroutineContext(Dispatchers.IO) .build() } @@ -117,7 +118,7 @@ private fun foodDatabase(): FoodDatabase { val dbFile = File(System.getProperty("java.io.tmpdir"), "flocon_food_database.db") return Room.databaseBuilder(name = dbFile.absolutePath) .fallbackToDestructiveMigration(dropAllTables = true) - .setDriver(BundledSQLiteDriver()) + .setDriver(BundledSQLiteDriver().floconLogs(id = "food_jvm")) .setQueryCoroutineContext(Dispatchers.IO) .build() } diff --git a/FloconAndroid/sample/ios/src/iosMain/kotlin/com/flocon/sample/ios/Databases.kt b/FloconAndroid/sample/ios/src/iosMain/kotlin/com/flocon/sample/ios/Databases.kt index 0284a87ac..2c5040525 100644 --- a/FloconAndroid/sample/ios/src/iosMain/kotlin/com/flocon/sample/ios/Databases.kt +++ b/FloconAndroid/sample/ios/src/iosMain/kotlin/com/flocon/sample/ios/Databases.kt @@ -2,6 +2,7 @@ package com.flocon.sample.ios import androidx.room3.Room import androidx.sqlite.driver.NativeSQLiteDriver +import io.github.openflocon.flocon.database.room3.floconLogs import io.github.openflocon.flocon.Flocon import io.github.openflocon.flocon.database.core.databasePlugin import io.github.openflocon.flocon.database.room3.register @@ -20,7 +21,7 @@ object Databases { return dogDatabase ?: run { val instance = Room.databaseBuilder(name = dbFile) .fallbackToDestructiveMigration(dropAllTables = true) - .setDriver(NativeSQLiteDriver()) + .setDriver(NativeSQLiteDriver().floconLogs(id = "dogs_ios")) .build() dogDatabase = instance Flocon.databasePlugin.register(id = "dogs_ios", displayName = "Dog Database", database = instance) @@ -34,7 +35,7 @@ object Databases { val dbFile = "${documentDirectory()}/food_database.db" return foodDatabase ?: run { val instance = Room.databaseBuilder(name = dbFile) - .setDriver(NativeSQLiteDriver()) + .setDriver(NativeSQLiteDriver().floconLogs(id = "food_ios")) .build() foodDatabase = instance Flocon.databasePlugin.register(id = "food_ios", displayName = "Food Database", database = instance) diff --git a/FloconAndroid/sample/wasm/src/wasmJsMain/kotlin/com/flocon/sample/wasm/Databases.kt b/FloconAndroid/sample/wasm/src/wasmJsMain/kotlin/com/flocon/sample/wasm/Databases.kt index 5675ddc15..11681727a 100644 --- a/FloconAndroid/sample/wasm/src/wasmJsMain/kotlin/com/flocon/sample/wasm/Databases.kt +++ b/FloconAndroid/sample/wasm/src/wasmJsMain/kotlin/com/flocon/sample/wasm/Databases.kt @@ -2,6 +2,7 @@ package com.flocon.sample.wasm import androidx.room3.Room import androidx.sqlite.driver.web.WebWorkerSQLiteDriver +import io.github.openflocon.flocon.database.room3.floconLogs import io.github.openflocon.flocon.Flocon import io.github.openflocon.flocon.database.core.databasePlugin import io.github.openflocon.flocon.database.room3.register @@ -22,14 +23,14 @@ object Databases { val dogDatabase: DogDatabase by lazy { Room.databaseBuilder(name = "dogs_database.db") - .setDriver(driver) + .setDriver(driver.floconLogs(id = "dogs_wasm")) .build() .also { Flocon.databasePlugin.register(id = "dogs_wasm", displayName = "dogs", database = it) } } val foodDatabase: FoodDatabase by lazy { Room.databaseBuilder(name = "food_database.db") - .setDriver(driver) + .setDriver(driver.floconLogs(id = "food_wasm")) .build() .also { Flocon.databasePlugin.register(id = "food_wasm", displayName = "food", database = it) } } diff --git a/docs2/database.md b/docs2/database.md index cf85c69a4..69558392a 100644 --- a/docs2/database.md +++ b/docs2/database.md @@ -140,7 +140,19 @@ Flocon can display all SQL queries executed by your app in real-time — great f The `id` given to `floconLogs` must be the one the database is registered with, otherwise the desktop app cannot show the queries with the database. - Room 3 has no query callback: use the manual logging. +=== "Room 3" + + Room 3 has no query callback: wrap its SQLite driver instead. + + ```kotlin + Room.databaseBuilder(context, "dogs_database") + .setDriver(AndroidSQLiteDriver().floconLogs(id = "dogs")) // io.github.openflocon.flocon.database.room3.floconLogs + .build() + .also { Flocon.databasePlugin.register(id = "dogs", displayName = "dogs", database = it) } + ``` + + Works with any driver (`BundledSQLiteDriver`, `NativeSQLiteDriver`, `WebWorkerSQLiteDriver`...). Each statement + is logged when it runs, with its arguments. Same rule as Room 2: the `id` is the one the database is registered with. === "Manual Query Logging" From 53defac4633b8fcf602fe0bf61f66b06b14ccd1c Mon Sep 17 00:00:00 2001 From: Florent Champigny Date: Fri, 30 Oct 2026 23:39:35 +0100 Subject: [PATCH 3/3] docs: [DATABASE] Say which Room 3 driver was checked --- docs2/database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs2/database.md b/docs2/database.md index 69558392a..d9b9d1005 100644 --- a/docs2/database.md +++ b/docs2/database.md @@ -151,7 +151,7 @@ Flocon can display all SQL queries executed by your app in real-time — great f .also { Flocon.databasePlugin.register(id = "dogs", displayName = "dogs", database = it) } ``` - Works with any driver (`BundledSQLiteDriver`, `NativeSQLiteDriver`, `WebWorkerSQLiteDriver`...). Each statement + Wraps any driver (`BundledSQLiteDriver`, `NativeSQLiteDriver`, `WebWorkerSQLiteDriver`...), checked on Android with `AndroidSQLiteDriver`. Each statement is logged when it runs, with its arguments. Same rule as Room 2: the `id` is the one the database is registered with. === "Manual Query Logging"