diff --git a/library/db/room-schemas/org.cru.godtools.db.room.GodToolsRoomDatabase/14.json b/library/db/room-schemas/org.cru.godtools.db.room.GodToolsRoomDatabase/14.json new file mode 100644 index 0000000000..29bc95f55c --- /dev/null +++ b/library/db/room-schemas/org.cru.godtools.db.room.GodToolsRoomDatabase/14.json @@ -0,0 +1,661 @@ +{ + "formatVersion": 1, + "database": { + "version": 14, + "identityHash": "61b9f620ca5749cf1e85d2e93b08fdcf", + "entities": [ + { + "tableName": "attachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `tool` TEXT, `filename` TEXT, `sha256` TEXT, `isDownloaded` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`), FOREIGN KEY(`tool`) REFERENCES `tools`(`code`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tool", + "columnName": "tool", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sha256", + "columnName": "sha256", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDownloaded", + "columnName": "isDownloaded", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_attachments_tool", + "unique": false, + "columnNames": [ + "tool" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_attachments_tool` ON `${TABLE_NAME}` (`tool`)" + } + ], + "foreignKeys": [ + { + "table": "tools", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "tool" + ], + "referencedColumns": [ + "code" + ] + } + ] + }, + { + "tableName": "languages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`code` TEXT NOT NULL, `id` INTEGER NOT NULL, `name` TEXT, `isAdded` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isAdded", + "columnName": "isAdded", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "downloadedFiles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`filename` TEXT NOT NULL, PRIMARY KEY(`filename`))", + "fields": [ + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "filename" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "followups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `email` TEXT NOT NULL, `destination` INTEGER NOT NULL, `language` TEXT NOT NULL, `createdAt` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "destination", + "columnName": "destination", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "global_activity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`users` INTEGER NOT NULL, `countries` INTEGER NOT NULL, `launches` INTEGER NOT NULL, `gospelPresentations` INTEGER NOT NULL, `id` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "users", + "columnName": "users", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "countries", + "columnName": "countries", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "launches", + "columnName": "launches", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "gospelPresentations", + "columnName": "gospelPresentations", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "tools", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `code` TEXT NOT NULL, `type` TEXT NOT NULL, `name` TEXT, `category` TEXT, `description` TEXT, `shares` INTEGER NOT NULL DEFAULT 0, `pendingShares` INTEGER NOT NULL DEFAULT 0, `bannerId` INTEGER, `detailsBannerId` INTEGER, `detailsBannerAnimationId` INTEGER, `detailsBannerYoutubeVideoId` TEXT, `isScreenShareDisabled` INTEGER NOT NULL DEFAULT false, `defaultOrder` INTEGER NOT NULL DEFAULT 0, `order` INTEGER NOT NULL DEFAULT 2147483647, `metatoolCode` TEXT, `defaultVariantCode` TEXT, `isFavorite` INTEGER NOT NULL DEFAULT false, `isHidden` INTEGER NOT NULL DEFAULT false, `isSpotlight` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`code`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shares", + "columnName": "shares", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "pendingShares", + "columnName": "pendingShares", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "bannerId", + "columnName": "bannerId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "detailsBannerId", + "columnName": "detailsBannerId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "detailsBannerAnimationId", + "columnName": "detailsBannerAnimationId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "detailsBannerYoutubeVideoId", + "columnName": "detailsBannerYoutubeVideoId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isScreenShareDisabled", + "columnName": "isScreenShareDisabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "defaultOrder", + "columnName": "defaultOrder", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "2147483647" + }, + { + "fieldPath": "metatoolCode", + "columnName": "metatoolCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "defaultVariantCode", + "columnName": "defaultVariantCode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isFavorite", + "columnName": "isFavorite", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isHidden", + "columnName": "isHidden", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "isSpotlight", + "columnName": "isSpotlight", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "code" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "training_tips", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`isCompleted` INTEGER NOT NULL, `isNew` INTEGER NOT NULL, `tool` TEXT NOT NULL, `locale` TEXT NOT NULL, `tipId` TEXT NOT NULL, PRIMARY KEY(`tool`, `locale`, `tipId`))", + "fields": [ + { + "fieldPath": "isCompleted", + "columnName": "isCompleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNew", + "columnName": "isNew", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key.tool", + "columnName": "tool", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "key.locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "key.tipId", + "columnName": "tipId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tool", + "locale", + "tipId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "translations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `tool` TEXT NOT NULL, `locale` TEXT NOT NULL, `version` INTEGER NOT NULL, `name` TEXT, `description` TEXT, `tagline` TEXT, `toolDetailsConversationStarters` TEXT, `toolDetailsOutline` TEXT, `toolDetailsBibleReferences` TEXT, `manifestFileName` TEXT, `isDownloaded` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`), FOREIGN KEY(`tool`) REFERENCES `tools`(`code`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`locale`) REFERENCES `languages`(`code`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tool", + "columnName": "tool", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "locale", + "columnName": "locale", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tagline", + "columnName": "tagline", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "toolDetailsConversationStarters", + "columnName": "toolDetailsConversationStarters", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "toolDetailsOutline", + "columnName": "toolDetailsOutline", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "toolDetailsBibleReferences", + "columnName": "toolDetailsBibleReferences", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "manifestFileName", + "columnName": "manifestFileName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDownloaded", + "columnName": "isDownloaded", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_translations_tool_locale", + "unique": false, + "columnNames": [ + "tool", + "locale" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_translations_tool_locale` ON `${TABLE_NAME}` (`tool`, `locale`)" + }, + { + "name": "index_translations_tool_locale_version", + "unique": false, + "columnNames": [ + "tool", + "locale", + "version" + ], + "orders": [ + "ASC", + "ASC", + "DESC" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_translations_tool_locale_version` ON `${TABLE_NAME}` (`tool` ASC, `locale` ASC, `version` DESC)" + } + ], + "foreignKeys": [ + { + "table": "tools", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "tool" + ], + "referencedColumns": [ + "code" + ] + }, + { + "table": "languages", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "locale" + ], + "referencedColumns": [ + "code" + ] + } + ] + }, + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `ssoGuid` TEXT, `name` TEXT, `createdAt` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ssoGuid", + "columnName": "ssoGuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "user_counters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `count` INTEGER NOT NULL, `decayedCount` REAL NOT NULL, `delta` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "count", + "columnName": "count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "decayedCount", + "columnName": "decayedCount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "delta", + "columnName": "delta", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "last_sync_times", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `time` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '61b9f620ca5749cf1e85d2e93b08fdcf')" + ] + } +} \ No newline at end of file diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabase.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabase.kt index 3b0e4d1853..a45782a606 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabase.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabase.kt @@ -2,8 +2,10 @@ package org.cru.godtools.db.room import androidx.room.AutoMigration import androidx.room.Database +import androidx.room.RenameColumn import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.AutoMigrationSpec import org.ccci.gto.android.common.androidx.room.converter.Java8TimeConverters import org.ccci.gto.android.common.androidx.room.converter.LocaleConverter import org.cru.godtools.db.room.dao.AttachmentsDao @@ -41,7 +43,7 @@ import org.cru.godtools.db.room.repository.UserCountersRoomRepository import org.cru.godtools.db.room.repository.UserRoomRepository @Database( - version = 13, + version = 14, entities = [ AttachmentEntity::class, LanguageEntity::class, @@ -68,6 +70,7 @@ import org.cru.godtools.db.room.repository.UserRoomRepository AutoMigration(from = 10, to = 11), AutoMigration(from = 11, to = 12), AutoMigration(from = 12, to = 13), + AutoMigration(from = 13, to = 14, spec = Migration14::class), ], ) @TypeConverters(Java8TimeConverters::class, LocaleConverter::class) @@ -125,7 +128,11 @@ internal abstract class GodToolsRoomDatabase : RoomDatabase() { * 11: 2023-05-15 * 12: 2023-06-08 * 13: 2023-09-18 + * 14: 2023-09-18 */ internal fun RoomDatabase.Builder.enableMigrations() = fallbackToDestructiveMigration() + +@RenameColumn(tableName = "tools", fromColumnName = "isAdded", toColumnName = "isFavorite") +internal class Migration14 : AutoMigrationSpec // endregion Migrations diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt index bb1817f7d2..1096e42349 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/dao/ToolsDao.kt @@ -42,8 +42,8 @@ internal interface ToolsDao { fun insertOrIgnoreTools(tools: Collection) @Upsert(entity = ToolEntity::class) suspend fun upsertSyncTools(tools: Collection) - @Query("UPDATE tools SET isAdded = :isAdded WHERE code = :code") - suspend fun updateIsAdded(code: String, isAdded: Boolean) + @Query("UPDATE tools SET isFavorite = :isFavorite WHERE code = :code") + suspend fun updateIsAdded(code: String, isFavorite: Boolean) @Query("UPDATE tools SET `order` = ${Int.MAX_VALUE}") fun resetToolOrder() @Query("UPDATE tools SET `order` = :order WHERE code = :code") diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/entity/ToolEntity.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/entity/ToolEntity.kt index 4aedb3129e..039729e74a 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/entity/ToolEntity.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/entity/ToolEntity.kt @@ -31,7 +31,7 @@ internal class ToolEntity( val metatoolCode: String? = null, val defaultVariantCode: String? = null, @ColumnInfo(defaultValue = "false") - val isAdded: Boolean = false, + val isFavorite: Boolean = false, @ColumnInfo(defaultValue = "false") val isHidden: Boolean = false, @ColumnInfo(defaultValue = "false") @@ -55,7 +55,7 @@ internal class ToolEntity( order = tool.order, metatoolCode = tool.metatoolCode, defaultVariantCode = tool.defaultVariantCode, - isAdded = tool.isFavorite, + isFavorite = tool.isFavorite, isHidden = tool.isHidden, isSpotlight = tool.isSpotlight, ) @@ -78,7 +78,7 @@ internal class ToolEntity( it.order = order it.metatoolCode = metatoolCode it.defaultVariantCode = defaultVariantCode - it.isFavorite = isAdded + it.isFavorite = isFavorite it.isHidden = isHidden it.isSpotlight = isSpotlight } diff --git a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt index 470e2480ee..dd32efcb55 100644 --- a/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt +++ b/library/db/src/main/kotlin/org/cru/godtools/db/room/repository/ToolsRoomRepository.kt @@ -55,7 +55,7 @@ internal abstract class ToolsRoomRepository(private val db: GodToolsRoomDatabase @Transaction override suspend fun deleteIfNotFavorite(code: String) { - val tool = dao.findTool(code)?.takeUnless { it.isAdded } ?: return + val tool = dao.findTool(code)?.takeUnless { it.isFavorite } ?: return dao.delete(tool) } } diff --git a/library/db/src/test/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabaseMigrationIT.kt b/library/db/src/test/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabaseMigrationIT.kt index adf02f8875..7fc1583bf3 100644 --- a/library/db/src/test/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabaseMigrationIT.kt +++ b/library/db/src/test/kotlin/org/cru/godtools/db/room/GodToolsRoomDatabaseMigrationIT.kt @@ -277,4 +277,23 @@ class GodToolsRoomDatabaseMigrationIT { // run migration helper.runMigrationsAndValidate(GodToolsRoomDatabase.DATABASE_NAME, 13, true, *MIGRATIONS).use {} } + + @Test + fun testMigrate13To14() { + // create v13 database + helper.createDatabase(GodToolsRoomDatabase.DATABASE_NAME, 13).use { db -> + db.execSQL("""INSERT INTO tools (id, code, type, isAdded) VALUES (1, "a", "TRACT", 1)""") + } + + // run migration + helper.runMigrationsAndValidate(GodToolsRoomDatabase.DATABASE_NAME, 14, true, *MIGRATIONS).use { db -> + db.query("SELECT id, code, isFavorite FROM tools").use { + assertEquals(1, it.count) + it.moveToFirst() + assertEquals(1, it.getIntOrNull(0)) + assertEquals("a", it.getStringOrNull(1)) + assertEquals(1, it.getIntOrNull(2)) + } + } + } }