From 86d0d06e76f74bc6de79df21acce18b65938ffc1 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:09:08 +0000 Subject: [PATCH] sync: fail loudly on upsert column/value drift Review follow-ups for the replay gain slice. `upsertChanged` never checked that its hand-maintained column list and the row values line up, and SQLite binds a missing trailing value as NULL without error: adding a column to `songColumns` but not to `Song.values()` would have written NULLs silently. It now asserts the sizes match before preparing, so that mistake fails the sync instead. The replay gain sync test also now re-syncs identical values and asserts no rows are rewritten, pinning the REAL change-detection clause; an update there rewrites the `song_search` FTS row for no reason. --- app/src/main/kotlin/com/subtracks/data/sync/SyncService.kt | 1 + .../test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/kotlin/com/subtracks/data/sync/SyncService.kt b/app/src/main/kotlin/com/subtracks/data/sync/SyncService.kt index 042ec951..3dd1cb57 100644 --- a/app/src/main/kotlin/com/subtracks/data/sync/SyncService.kt +++ b/app/src/main/kotlin/com/subtracks/data/sync/SyncService.kt @@ -382,6 +382,7 @@ private suspend fun PooledConnection.upsertChanged( values: (T) -> List, ) { if (rows.isEmpty()) return + check(values(rows.first()).size == columns.size) { "column/value mismatch for $table" } val updates = columns.filterNot { it in key } val sql = "INSERT INTO $table (${columns.joinToString()}) VALUES (${columns.joinToString { "?" }}) " + diff --git a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt index 94a32a2e..707faaf3 100644 --- a/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/sync/SyncServiceTest.kt @@ -913,6 +913,10 @@ class SyncServiceTest { assertEquals(0.98f, stored.trackPeak) assertEquals(0.99f, stored.albumPeak) + val before = totalChanges() + SyncService(db, source).sync() + assertEquals(before, totalChanges()) + source.songs = listOf(song("s1")) SyncService(db, source).sync()