From 60fff72f3b6c4898fd472017d211995f61abc640 Mon Sep 17 00:00:00 2001 From: stslex Date: Thu, 20 Aug 2026 22:05:08 +0300 Subject: [PATCH 1/3] kmp(database): Android trades the per-OEM SQLite for one bundled build, behind its own bisect point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildAppDatabase moves to BundledSQLiteDriver (SQLite 3.50.x on every device; main-db and WAL formats frozen, so existing installations open unchanged), and the flip's oracle moves with it: all 5 device-test classes and the app:app in-memory provider (MetroTestRule) now run the production driver. The Robolectric surfaces deliberately do NOT flip, and the reason is measured, not assumed: the bundled android variant carries Android-ABI natives only, and loading it on a desktop JVM dies with UnsatisfiedLinkError (probed on ExerciseDaoGetAllTest before deciding). RepositoryTestEnv, BaseDatabaseTest and the snapshot host test pin AndroidSQLiteDriver with the reason in place — Robolectric was already inadmissible as a driver oracle (two recorded false negatives on rollback). Pre-flip audit of all 105 @Query sites + every raw-SQL site found zero version-sensitive constructs and zero double-quoted string literals (the classic DQS breakage); the one pre-existing arbitrary-row query (getRecentSessionsForExercise's bare column under GROUP BY) is recorded, not regressed. SessionDao's PR-batch comment now states ROW_NUMBER() as available-but-not-taken; the snapshot package's framework-SQLite peek and checkpoint interop is documented at the builder and remains the device-restore pass's territory. Gate, under the flipped driver: 28/28 device tests green on Pixel_6_API_34 (the 7 migration cases apply Migration6 to a real v5 file and validate against 6.json), the same 28 having run green under AndroidSQLiteDriver one commit earlier — both drivers, as the spec's §6 requires. app:app device Regression 41/41 (35 pinned + #232's six). Host tests unchanged: database 128, exercise 212. Full forced gate 3248/3248, detekt separate 56/56, repo counts identical to the parent commit (2230+165, 0 failures), goldens 456/0 touched, schemas untouched. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_011kNU4XxzXp6NpCnVUQA6yH --- .../src/main/kotlin/RoomLibraryConventionPlugin.kt | 13 ++++++++----- core/data/database-test/build.gradle.kts | 7 ++++++- .../data/database/testfixtures/RepositoryTestEnv.kt | 5 +++++ .../data/database_test/InMemoryDatabaseProvider.kt | 4 ++-- core/data/database/build.gradle.kts | 5 +++++ .../core/data/database/AppDatabaseMigrationTest.kt | 4 ++-- .../core/data/database/AtomicRollbackDeviceTest.kt | 4 ++-- .../core/data/database/InvalidationDeviceTest.kt | 4 ++-- .../core/data/database/Room3RoundTripDeviceTest.kt | 4 ++-- .../exercise/ExerciseDaoRecentlyTrainedTest.kt | 4 ++-- .../core/data/database/AppDatabaseFactory.kt | 12 ++++++++---- .../core/data/database/session/SessionDao.kt | 9 ++++++--- 12 files changed, 50 insertions(+), 25 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt index aac85df06..212d84c2e 100644 --- a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt @@ -71,11 +71,14 @@ class RoomLibraryConventionPlugin : Plugin { // where androidx.paging.PagingSource actually lives (phase-6 spec §0). add("commonMainImplementation", libs.findBundle("room").get()) add("commonMainImplementation", libs.findLibrary("androidx-paging-common").get()) - // The driver artifact is per-target by construction: its android variant carries - // AndroidSQLiteDriver, its Apple variants NativeSQLiteDriver, and no arrangement - // shares one across targets (phase-6 spec §6). iosMain gets a driver dependency - // the day an iOS composition root builds a database, not before. - add("androidMainImplementation", libs.findLibrary("androidx-sqlite-framework").get()) + // BundledSQLiteDriver: one SQLite build (3.50.x) on every device instead of the + // per-OEM system one (phase-6 spec §6; the flip commit's own gate). Per-target by + // construction — iosMain gets a driver dependency the day an iOS composition root + // builds a database, not before. Robolectric HOST tests cannot use bundled (the + // android variant ships Android-ABI natives only; measured UnsatisfiedLinkError), + // so a module whose host tests build databases pins sqlite-framework on + // androidHostTestImplementation itself. + add("androidMainImplementation", libs.findLibrary("androidx-sqlite-bundled").get()) // Room's KSP codegen runs once per compilation target; the compiler artifact // itself is JVM-only, which is fine — KSP always executes on the JVM. diff --git a/core/data/database-test/build.gradle.kts b/core/data/database-test/build.gradle.kts index 48b41980b..807477608 100644 --- a/core/data/database-test/build.gradle.kts +++ b/core/data/database-test/build.gradle.kts @@ -16,8 +16,13 @@ dependencies { api(project(":core:data:database")) api(libs.bundles.room) - // InMemoryDatabaseProvider builds a Room 3 DB and must setDriver(AndroidSQLiteDriver()). + // The two fixtures deliberately run DIFFERENT drivers. RepositoryTestEnv (Robolectric + // repository unit tests) pins AndroidSQLiteDriver: the bundled driver's android variant + // ships Android-ABI natives only and dies with UnsatisfiedLinkError on a desktop JVM + // (measured). InMemoryDatabaseProvider (on-device androidTest via MetroTestRule) runs + // BundledSQLiteDriver — the production driver since the flip. api(libs.androidx.sqlite.framework) + api(libs.androidx.sqlite.bundled) api(libs.androidx.test) // RepositoryTestEnv runs suspending seeds and exposes a CoroutineScope to its callers. api(libs.coroutines) diff --git a/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database/testfixtures/RepositoryTestEnv.kt b/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database/testfixtures/RepositoryTestEnv.kt index 3bd6ba69b..90c5fdb89 100644 --- a/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database/testfixtures/RepositoryTestEnv.kt +++ b/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database/testfixtures/RepositoryTestEnv.kt @@ -25,6 +25,11 @@ import org.jetbrains.annotations.TestOnly * required (the in-memory builder needs an Android `Context`); add the * `RobolectricExtension` to your test class along with * `@Config(application = RepositoryTestEnv.TestApplication::class, sdk = [33])`. + * + * Stays on [AndroidSQLiteDriver] after the production flip to `BundledSQLiteDriver`: the + * bundled android variant carries Android-ABI natives only, and loading it under Robolectric + * on a desktop JVM fails with `UnsatisfiedLinkError` (measured). Driver behaviour is a + * device-suite concern; this fixture's oracle value is repository logic over a real schema. */ @TestOnly class RepositoryTestEnv { diff --git a/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database_test/InMemoryDatabaseProvider.kt b/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database_test/InMemoryDatabaseProvider.kt index b9c2e2fff..eaecae5d2 100644 --- a/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database_test/InMemoryDatabaseProvider.kt +++ b/core/data/database-test/src/main/kotlin/io/github/stslex/workeeper/core/data/database_test/InMemoryDatabaseProvider.kt @@ -3,7 +3,7 @@ package io.github.stslex.workeeper.core.data.database_test import android.content.Context import androidx.room3.Room -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import io.github.stslex.workeeper.core.data.database.AppDatabase /** @@ -22,7 +22,7 @@ object InMemoryDatabaseProvider { .inMemoryDatabaseBuilder( context, ) - .setDriver(AndroidSQLiteDriver()) + .setDriver(BundledSQLiteDriver()) .allowMainThreadQueries() .build() } diff --git a/core/data/database/build.gradle.kts b/core/data/database/build.gradle.kts index d606a02bb..579e0e0da 100644 --- a/core/data/database/build.gradle.kts +++ b/core/data/database/build.gradle.kts @@ -41,6 +41,11 @@ dependencies { "androidHostTestImplementation"(libs.robolectric) "androidHostTestImplementation"(libs.robolectric.junit5.extension) "androidHostTestImplementation"(libs.androidx.test) + // Host tests stay on AndroidSQLiteDriver: the bundled driver's android variant carries + // Android-ABI natives only and dies with UnsatisfiedLinkError under Robolectric on a + // desktop JVM (measured). Robolectric is not an admissible driver oracle anyway — the + // device suite is where the production driver is exercised. + "androidHostTestImplementation"(libs.androidx.sqlite.framework) // MigrationsRegistryTest introspects the registry against room-testing's Migration surface. "androidHostTestImplementation"(libs.androidx.room.testing) diff --git a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseMigrationTest.kt b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseMigrationTest.kt index 81e05f342..455e0a060 100644 --- a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseMigrationTest.kt +++ b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseMigrationTest.kt @@ -2,7 +2,7 @@ package io.github.stslex.workeeper.core.data.database import androidx.room3.testing.MigrationTestHelper -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.sqlite.execSQL import androidx.test.platform.app.InstrumentationRegistry import io.github.stslex.workeeper.core.data.database.migration.Migration6 @@ -37,7 +37,7 @@ internal class AppDatabaseMigrationTest { val helper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), InstrumentationRegistry.getInstrumentation().targetContext.getDatabasePath(TEST_DB), - AndroidSQLiteDriver(), + BundledSQLiteDriver(), AppDatabase::class, ) diff --git a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AtomicRollbackDeviceTest.kt b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AtomicRollbackDeviceTest.kt index 7d3cddf17..10e7e0204 100644 --- a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AtomicRollbackDeviceTest.kt +++ b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/AtomicRollbackDeviceTest.kt @@ -5,7 +5,7 @@ import android.content.Context import androidx.room3.Room import androidx.room3.immediateTransaction import androidx.room3.useWriterConnection -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.stslex.workeeper.core.core.coroutine.asyncForEach @@ -79,7 +79,7 @@ internal class AtomicRollbackDeviceTest { // Real file-backed DB on the device — NOT in-memory, so transaction/connection // semantics match production, not Robolectric's shadow SQLite. database = Room.databaseBuilder(context, PROBE_DB) - .setDriver(AndroidSQLiteDriver()) + .setDriver(BundledSQLiteDriver()) .build() } diff --git a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/InvalidationDeviceTest.kt b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/InvalidationDeviceTest.kt index 1801805b0..7782332c4 100644 --- a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/InvalidationDeviceTest.kt +++ b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/InvalidationDeviceTest.kt @@ -5,7 +5,7 @@ import android.content.Context import androidx.room3.Room import androidx.room3.immediateTransaction import androidx.room3.useWriterConnection -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.stslex.workeeper.core.data.database.tag.TagEntity @@ -53,7 +53,7 @@ internal class InvalidationDeviceTest { context = ApplicationProvider.getApplicationContext() context.deleteDatabase(PROBE_DB) database = Room.databaseBuilder(context, PROBE_DB) - .setDriver(AndroidSQLiteDriver()) + .setDriver(BundledSQLiteDriver()) .build() } diff --git a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/Room3RoundTripDeviceTest.kt b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/Room3RoundTripDeviceTest.kt index d19e8f811..d68ce17d8 100644 --- a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/Room3RoundTripDeviceTest.kt +++ b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/Room3RoundTripDeviceTest.kt @@ -6,7 +6,7 @@ import androidx.paging.PagingSource import androidx.room3.Room import androidx.room3.immediateTransaction import androidx.room3.useWriterConnection -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.stslex.workeeper.core.data.database.exercise.ExerciseEntity @@ -58,7 +58,7 @@ internal class Room3RoundTripDeviceTest { private fun openDb(): AppDatabase = Room.databaseBuilder(context, ROUNDTRIP_DB) - .setDriver(AndroidSQLiteDriver()) + .setDriver(BundledSQLiteDriver()) .build() @Before diff --git a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/exercise/ExerciseDaoRecentlyTrainedTest.kt b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/exercise/ExerciseDaoRecentlyTrainedTest.kt index 75a945005..33c04e6c2 100644 --- a/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/exercise/ExerciseDaoRecentlyTrainedTest.kt +++ b/core/data/database/src/androidDeviceTest/kotlin/io/github/stslex/workeeper/core/data/database/exercise/ExerciseDaoRecentlyTrainedTest.kt @@ -3,7 +3,7 @@ package io.github.stslex.workeeper.core.data.database.exercise import android.app.Application import androidx.room3.Room -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.github.stslex.workeeper.core.data.database.AppDatabase @@ -47,7 +47,7 @@ internal class ExerciseDaoRecentlyTrainedTest { database = Room.inMemoryDatabaseBuilder( ApplicationProvider.getApplicationContext(), ) - .setDriver(AndroidSQLiteDriver()) + .setDriver(BundledSQLiteDriver()) .allowMainThreadQueries() .build() } diff --git a/core/data/database/src/androidMain/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseFactory.kt b/core/data/database/src/androidMain/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseFactory.kt index 1a05c56c7..d10e73198 100644 --- a/core/data/database/src/androidMain/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseFactory.kt +++ b/core/data/database/src/androidMain/kotlin/io/github/stslex/workeeper/core/data/database/AppDatabaseFactory.kt @@ -3,7 +3,7 @@ package io.github.stslex.workeeper.core.data.database import android.content.Context import androidx.room3.Room -import androidx.sqlite.driver.AndroidSQLiteDriver +import androidx.sqlite.driver.bundled.BundledSQLiteDriver import io.github.stslex.workeeper.core.data.database.migration.MIGRATIONS /** @@ -38,8 +38,12 @@ fun buildAppDatabase(context: Context): AppDatabase = Room context = context, name = AppDatabase.NAME, ) - // Room 3 requires an explicit driver; AndroidSQLiteDriver is the framework SQLite - // implementation Room 2.8.4 used implicitly, so the on-disk format is unchanged. - .setDriver(AndroidSQLiteDriver()) + // Room 3 requires an explicit driver. BundledSQLiteDriver ships one SQLite build (3.50.x) + // to every device instead of the per-OEM, per-API-level system one — the main-db and WAL + // file formats are frozen, so existing installations open unchanged. The snapshot/ package + // still opens the same file through framework SQLite (android.database.sqlite) for its + // pre-migration peek and checkpoint; that cross-library interop is deliberate and its + // paths are exercised by the recovery flow, not by this builder. + .setDriver(BundledSQLiteDriver()) .apply { MIGRATIONS.forEach { addMigrations(it) } } .build() diff --git a/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt b/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt index de0d87737..aa0351c14 100644 --- a/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt +++ b/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt @@ -74,9 +74,12 @@ private const val PR_SINGLE_SQL = """ /** * Batch PR: *every* eligible candidate for every requested exercise, grouped by exercise and - * ordered so the consumer takes `.first()` per group. No `LIMIT`/window function — `minSdk 28` - * ships SQLite 3.22 and `ROW_NUMBER()` needs 3.25 (the bundled-SQLite dependency is declared - * but inert; `AndroidSQLiteDriver` uses framework SQLite). + * ordered so the consumer takes `.first()` per group. No `LIMIT`/window function in the SQL — + * written when production ran the system SQLite (3.22 at minSdk 28, no `ROW_NUMBER()` before + * 3.25). `BundledSQLiteDriver` now ships 3.50 everywhere, so a + * `ROW_NUMBER() OVER (PARTITION BY …)` rewrite is *available*; it is not taken in the driver + * flip, which is behaviour-neutral by design. The Robolectric host tests still run whatever + * SQLite Robolectric bundles, so any rewrite must keep its device coverage. */ private const val PR_BATCH_SQL = """ $PR_ROW_SELECT From 88c5f142da1e422e550f07634496b2a2c8bfe13a Mon Sep 17 00:00:00 2001 From: stslex Date: Fri, 21 Aug 2026 12:17:18 +0300 Subject: [PATCH 2/3] review: describe active bundled SQLite wiring --- .../src/main/kotlin/RoomLibraryConventionPlugin.kt | 4 ++-- core/data/database/build.gradle.kts | 2 +- gradle/libs.versions.toml | 13 ++++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt index 212d84c2e..bb22d2d56 100644 --- a/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/RoomLibraryConventionPlugin.kt @@ -54,8 +54,8 @@ class RoomLibraryConventionPlugin : Plugin { private fun Project.configureAndroid() { dependencies { implementationBundle("room") - // Room 3 requires a SQLiteDriver via setDriver(); AndroidSQLiteDriver - // (framework SQLite) is referenced in AppDatabaseFactory and the test builders. + // Room 3 requires a SQLiteDriver via setDriver(); classic Android Room modules use + // the framework artifact, while KMP modules select their driver in configureKmp(). implementation("androidx-sqlite-framework") ksp("androidx-room-compiler") diff --git a/core/data/database/build.gradle.kts b/core/data/database/build.gradle.kts index 579e0e0da..eeb76db09 100644 --- a/core/data/database/build.gradle.kts +++ b/core/data/database/build.gradle.kts @@ -17,7 +17,7 @@ metro { } // The Room surface (entities, DAOs, converters, migrations, export) is commonMain; only what is -// platform-typed stays in androidMain — buildAppDatabase (Context + AndroidSQLiteDriver) and the +// platform-typed stays in androidMain — buildAppDatabase (Context + BundledSQLiteDriver) and the // snapshot/ package (raw android.database.sqlite + java.io.File, deliberately outside Room). kotlin { sourceSets { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 36668503e..256c9eae1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -48,10 +48,9 @@ fbCrashlytics = "3.0.7" # room = "2.8.4" was the androidx.room 2.x line; superseded by the room3 (androidx.room3) # coordinates below, which are the applied ones. The 2.x version key is gone with its coords. -# androidx.sqlite 2.7.0 (transitive of room3-runtime): sqlite-framework carries the -# AndroidSQLiteDriver, now REQUIRED via setDriver() on every RoomDatabase.Builder in -# Room 3. sqlite-bundled (K/N BundledSQLiteDriver) stays declared but INERT — Android -# uses the framework driver; bundled lands when C.1 converts the DB module to K/N. +# androidx.sqlite 2.7.0 (transitive of room3-runtime): every Room 3 builder selects a driver +# explicitly. sqlite-bundled supplies the production Android driver for KMP Room modules; +# sqlite-framework remains for classic Android modules and Robolectric host-test fixtures. room3 = "3.0.0" sqlite = "2.7.0" serialization = "1.11.0" @@ -180,9 +179,9 @@ androidx-room-paging = { group = "androidx.room3", name = "room3-paging", versio androidx-room-compiler = { group = "androidx.room3", name = "room3-compiler", version.ref = "room3" } androidx-room-testing = { group = "androidx.room3", name = "room3-testing", version.ref = "room3" } -# AndroidSQLiteDriver (framework SQLite) — the Room 3 setDriver() driver on Android. +# AndroidSQLiteDriver (framework SQLite) — classic Android modules and JVM host-test fixtures. androidx-sqlite-framework = { group = "androidx.sqlite", name = "sqlite-framework", version.ref = "sqlite" } -# K/N BundledSQLiteDriver — declared but inert (Android does not need it; lands with C.1). +# BundledSQLiteDriver — the production Android driver for KMP Room modules. androidx-sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" } androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } @@ -303,4 +302,4 @@ test = [ room = [ "androidx-room-runtime", "androidx-room-paging" -] \ No newline at end of file +] From 5362cc721195cc7ddf9f54400417e664ddae524e Mon Sep 17 00:00:00 2001 From: stslex Date: Fri, 21 Aug 2026 12:35:44 +0300 Subject: [PATCH 3/3] review: keep SessionDao rationale current --- .../core/data/database/session/SessionDao.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt b/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt index aa0351c14..4a201c2ed 100644 --- a/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt +++ b/core/data/database/src/commonMain/kotlin/io/github/stslex/workeeper/core/data/database/session/SessionDao.kt @@ -74,12 +74,11 @@ private const val PR_SINGLE_SQL = """ /** * Batch PR: *every* eligible candidate for every requested exercise, grouped by exercise and - * ordered so the consumer takes `.first()` per group. No `LIMIT`/window function in the SQL — - * written when production ran the system SQLite (3.22 at minSdk 28, no `ROW_NUMBER()` before - * 3.25). `BundledSQLiteDriver` now ships 3.50 everywhere, so a - * `ROW_NUMBER() OVER (PARTITION BY …)` rewrite is *available*; it is not taken in the driver - * flip, which is behaviour-neutral by design. The Robolectric host tests still run whatever - * SQLite Robolectric bundles, so any rewrite must keep its device coverage. + * ordered so the consumer takes `.first()` per group. A + * `ROW_NUMBER() OVER (PARTITION BY …)` rewrite is available under the production bundled + * driver, but any rewrite must retain device coverage because Robolectric host tests use a + * different SQLite engine. See kmp-phase-6-data-layer.md → §6 + * "The driver decision — decided." */ private const val PR_BATCH_SQL = """ $PR_ROW_SELECT