From 92e9adeceed5a56d76a52629934d97fc12b8dfd3 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Tue, 12 Aug 2025 11:21:30 +0100 Subject: [PATCH] Add DatabaseConfiguration.downgrade This allows clients to implement custom downgrade behavior (such as destructive migrations, etc). --- .../touchlab/sqliter/DatabaseConfiguration.kt | 3 ++ .../native/NativeDatabaseConnection.kt | 10 ++-- .../sqliter/native/NativeDatabaseManager.kt | 2 +- .../touchlab/sqliter/DatabaseManagerTest.kt | 46 +++++++++++++++---- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/DatabaseConfiguration.kt b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/DatabaseConfiguration.kt index 0f69cfe3..c0d28321 100644 --- a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/DatabaseConfiguration.kt +++ b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/DatabaseConfiguration.kt @@ -35,6 +35,9 @@ data class DatabaseConfiguration( val version: Int, val create: (DatabaseConnection) -> Unit, val upgrade: (DatabaseConnection, Int, Int) -> Unit = { _, _, _ -> }, + val downgrade: (DatabaseConnection, Int, Int) -> Unit = { _, initialVersion, version -> + error("Database version $initialVersion newer than config version $version") + }, val inMemory: Boolean = false, val journalMode: JournalMode = JournalMode.WAL, val extendedConfig:Extended = Extended(), diff --git a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseConnection.kt b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseConnection.kt index 3d67495a..b1787fc7 100644 --- a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseConnection.kt +++ b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseConnection.kt @@ -92,6 +92,7 @@ class NativeDatabaseConnection internal constructor( fun migrateIfNeeded( create: (DatabaseConnection) -> Unit, upgrade: (DatabaseConnection, Int, Int) -> Unit, + downgrade: (DatabaseConnection, Int, Int) -> Unit, version: Int ) { this.withTransaction { @@ -100,10 +101,11 @@ class NativeDatabaseConnection internal constructor( create(this) setVersion(version) } else if (initialVersion != version) { - if (initialVersion > version) - throw IllegalStateException("Database version $initialVersion newer than config version $version") - - upgrade(this, initialVersion, version) + if (initialVersion > version) { + downgrade(this, initialVersion, version) + } else { + upgrade(this, initialVersion, version) + } setVersion(version) } } diff --git a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseManager.kt b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseManager.kt index 4a1d794c..1f2b35d2 100644 --- a/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseManager.kt +++ b/sqliter-driver/src/nativeCommonMain/kotlin/co/touchlab/sqliter/native/NativeDatabaseManager.kt @@ -89,7 +89,7 @@ class NativeDatabaseManager(private val path:String, try { val version = configuration.version if(version != NO_VERSION_CHECK) - conn.migrateIfNeeded(configuration.create, configuration.upgrade, version) + conn.migrateIfNeeded(configuration.create, configuration.upgrade, configuration.downgrade, version) } catch (e: Exception) { // If this failed, we have to close the connection or we will end up leaking it. diff --git a/sqliter-driver/src/nativeCommonTest/kotlin/co/touchlab/sqliter/DatabaseManagerTest.kt b/sqliter-driver/src/nativeCommonTest/kotlin/co/touchlab/sqliter/DatabaseManagerTest.kt index 32949e23..470c2d69 100644 --- a/sqliter-driver/src/nativeCommonTest/kotlin/co/touchlab/sqliter/DatabaseManagerTest.kt +++ b/sqliter-driver/src/nativeCommonTest/kotlin/co/touchlab/sqliter/DatabaseManagerTest.kt @@ -149,8 +149,7 @@ class DatabaseManagerTest : BaseDatabaseTest(){ } @Test - fun downgradeNotAllowed(){ - val upgradeCalled = AtomicInt(0) + fun downgradeNotAllowedByDefault() { val config1 = DatabaseConfiguration( name = TEST_DB_NAME, version = 1, @@ -159,16 +158,11 @@ class DatabaseManagerTest : BaseDatabaseTest(){ execute() } }, - upgrade = { _, _, _ -> - upgradeCalled.increment() - }, loggingConfig = DatabaseConfiguration.Logging(logger = NoneLogger), ) - createDatabaseManager(config1).withConnection { } - assertEquals(0, upgradeCalled.value) - createDatabaseManager(config1.copy(version = 2)).withConnection { } - assertEquals(1, upgradeCalled.value) + createDatabaseManager(config1).withConnection { } + createDatabaseManager(config1.copy(version = 2)).withConnection { } var conn: DatabaseConnection? = null assertFails { @@ -178,6 +172,40 @@ class DatabaseManagerTest : BaseDatabaseTest(){ conn?.close() } + @Test + fun downgradeCalled() { + val upgradeCalled = AtomicInt(0) + val downgradeCalled = AtomicInt(0) + val config1 = DatabaseConfiguration( + name = TEST_DB_NAME, + version = 1, + create = { db -> + db.withStatement(TWO_COL) { + execute() + } + }, + upgrade = { _, _, _ -> + upgradeCalled.increment() + }, + downgrade = { _, _, _ -> + downgradeCalled.increment() + }, + loggingConfig = DatabaseConfiguration.Logging(logger = NoneLogger), + ) + + createDatabaseManager(config1).withConnection { } + assertEquals(0, upgradeCalled.value) + assertEquals(0, downgradeCalled.value) + + createDatabaseManager(config1.copy(version = 2)).withConnection { } + assertEquals(1, upgradeCalled.value) + assertEquals(0, downgradeCalled.value) + + createDatabaseManager(config1.copy(version = 1)).withConnection { } + assertEquals(1, upgradeCalled.value) + assertEquals(1, downgradeCalled.value) + } + @Test fun failedCreateRollsBack(){ val configFail = DatabaseConfiguration(