Prechádzať zdrojové kódy

Add room migration to change local_id to NOT NULL

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Álvaro Brey 2 rokov pred
rodič
commit
42b4519ab1

+ 2 - 0
app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt

@@ -40,6 +40,7 @@ import com.nextcloud.client.database.entity.ShareEntity
 import com.nextcloud.client.database.entity.SyncedFolderEntity
 import com.nextcloud.client.database.entity.UploadEntity
 import com.nextcloud.client.database.entity.VirtualEntity
+import com.nextcloud.client.database.migrations.Migration67to68
 import com.nextcloud.client.database.migrations.RoomMigration
 import com.nextcloud.client.database.migrations.addLegacyMigrations
 import com.owncloud.android.db.ProviderMeta
@@ -89,6 +90,7 @@ abstract class NextcloudDatabase : RoomDatabase() {
                     .allowMainThreadQueries()
                     .addLegacyMigrations(clock)
                     .addMigrations(RoomMigration())
+                    .addMigrations(Migration67to68())
                     .fallbackToDestructiveMigration()
                     .build()
             }

+ 101 - 0
app/src/main/java/com/nextcloud/client/database/migrations/DatabaseMigrationUtil.kt

@@ -0,0 +1,101 @@
+/*
+ * Nextcloud Android client application
+ *
+ *  @author Álvaro Brey
+ *  Copyright (C) 2023 Álvaro Brey
+ *  Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.nextcloud.client.database.migrations
+
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+object DatabaseMigrationUtil {
+
+    const val TYPE_TEXT = "TEXT"
+    const val TYPE_INTEGER = "INTEGER"
+    const val TYPE_INTEGER_PRIMARY_KEY = "INTEGER PRIMARY KEY"
+    const val KEYWORD_NOT_NULL = "NOT NULL"
+
+    /**
+     * Utility method to add or remove columns from a table
+     *
+     * See individual functions for more details
+     *
+     * @param newColumns Map of column names and types on the NEW table
+     * @param selectTransform a function that transforms the select statement. This can be used to change the values
+     * when copying, such as for removing nulls
+     */
+    fun migrateTable(
+        database: SupportSQLiteDatabase,
+        tableName: String,
+        newColumns: Map<String, String>,
+        selectTransform: ((String) -> String)? = null
+    ) {
+        require(newColumns.isNotEmpty())
+        val newTableTempName = "${tableName}_new"
+        createNewTable(database, newTableTempName, newColumns)
+        copyData(database, tableName, newTableTempName, newColumns.keys, selectTransform)
+        replaceTable(database, tableName, newTableTempName)
+    }
+
+    /**
+     * Utility method to create a new table with the given columns
+     */
+    private fun createNewTable(
+        database: SupportSQLiteDatabase,
+        newTableName: String,
+        columns: Map<String, String>
+    ) {
+        val columnsString = columns.entries.joinToString(",") { "${it.key} ${it.value}" }
+        database.execSQL("CREATE TABLE $newTableName ($columnsString)")
+    }
+
+    /**
+     * Utility method to copy data from an old table to a new table. Only the columns in [columnNames] will be copied
+     *
+     * @param selectTransform a function that transforms the select statement. This can be used to change the values
+     * when copying, such as for removing nulls
+     */
+    private fun copyData(
+        database: SupportSQLiteDatabase,
+        tableName: String,
+        newTableName: String,
+        columnNames: Iterable<String>,
+        selectTransform: ((String) -> String)? = null
+    ) {
+        val selectColumnsString = columnNames.joinToString(",", transform = selectTransform)
+        val destColumnsString = columnNames.joinToString(",")
+
+        database.execSQL(
+            "INSERT INTO $newTableName ($destColumnsString) " +
+                "SELECT $selectColumnsString FROM $tableName"
+        )
+    }
+
+    /**
+     * Utility method to replace an old table with a new one, essentially deleting the old one and renaming the new one
+     */
+    private fun replaceTable(
+        database: SupportSQLiteDatabase,
+        tableName: String,
+        newTableTempName: String
+    ) {
+        database.execSQL("DROP TABLE $tableName")
+        database.execSQL("ALTER TABLE $newTableTempName RENAME TO $tableName")
+    }
+}

+ 95 - 0
app/src/main/java/com/nextcloud/client/database/migrations/Migration67to68.kt

@@ -0,0 +1,95 @@
+/*
+ * Nextcloud Android client application
+ *
+ *  @author Álvaro Brey
+ *  Copyright (C) 2023 Álvaro Brey
+ *  Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.nextcloud.client.database.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.KEYWORD_NOT_NULL
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.TYPE_INTEGER
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.TYPE_TEXT
+
+/**
+ * Migration from version 67 to 68.
+ *
+ * This migration makes the local_id column NOT NULL, with -1 as a default value.
+ */
+@Suppress("MagicNumber")
+class Migration67to68 : Migration(67, 68) {
+    override fun migrate(database: SupportSQLiteDatabase) {
+        val tableName = "filelist"
+        val newTableTempName = "${tableName}_new"
+        val newColumns = mapOf(
+            "_id" to DatabaseMigrationUtil.TYPE_INTEGER_PRIMARY_KEY,
+            "filename" to TYPE_TEXT,
+            "encrypted_filename" to TYPE_TEXT,
+            "path" to TYPE_TEXT,
+            "path_decrypted" to TYPE_TEXT,
+            "parent" to TYPE_INTEGER,
+            "created" to TYPE_INTEGER,
+            "modified" to TYPE_INTEGER,
+            "content_type" to TYPE_TEXT,
+            "content_length" to TYPE_INTEGER,
+            "media_path" to TYPE_TEXT,
+            "file_owner" to TYPE_TEXT,
+            "last_sync_date" to TYPE_INTEGER,
+            "last_sync_date_for_data" to TYPE_INTEGER,
+            "modified_at_last_sync_for_data" to TYPE_INTEGER,
+            "etag" to TYPE_TEXT,
+            "etag_on_server" to TYPE_TEXT,
+            "share_by_link" to TYPE_INTEGER,
+            "permissions" to TYPE_TEXT,
+            "remote_id" to TYPE_TEXT,
+            "local_id" to "$TYPE_INTEGER $KEYWORD_NOT_NULL DEFAULT -1",
+            "update_thumbnail" to TYPE_INTEGER,
+            "is_downloading" to TYPE_INTEGER,
+            "favorite" to TYPE_INTEGER,
+            "is_encrypted" to TYPE_INTEGER,
+            "etag_in_conflict" to TYPE_TEXT,
+            "shared_via_users" to TYPE_INTEGER,
+            "mount_type" to TYPE_INTEGER,
+            "has_preview" to TYPE_INTEGER,
+            "unread_comments_count" to TYPE_INTEGER,
+            "owner_id" to TYPE_TEXT,
+            "owner_display_name" to TYPE_TEXT,
+            "note" to TYPE_TEXT,
+            "sharees" to TYPE_TEXT,
+            "rich_workspace" to TYPE_TEXT,
+            "metadata_size" to TYPE_TEXT,
+            "locked" to TYPE_INTEGER,
+            "lock_type" to TYPE_INTEGER,
+            "lock_owner" to TYPE_TEXT,
+            "lock_owner_display_name" to TYPE_TEXT,
+            "lock_owner_editor" to TYPE_TEXT,
+            "lock_timestamp" to TYPE_INTEGER,
+            "lock_timeout" to TYPE_INTEGER,
+            "lock_token" to TYPE_TEXT
+        )
+
+        DatabaseMigrationUtil.migrateTable(database, "filelist", newColumns) { columnName ->
+            when (columnName) {
+                "local_id" -> "IFNULL(local_id, -1)"
+                else -> columnName
+            }
+        }
+    }
+}

+ 7 - 50
app/src/main/java/com/nextcloud/client/database/migrations/RoomMigration.kt

@@ -25,6 +25,9 @@ package com.nextcloud.client.database.migrations
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
 import com.nextcloud.client.database.NextcloudDatabase
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.TYPE_INTEGER
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.TYPE_INTEGER_PRIMARY_KEY
+import com.nextcloud.client.database.migrations.DatabaseMigrationUtil.TYPE_TEXT
 
 class RoomMigration : Migration(NextcloudDatabase.FIRST_ROOM_DB_VERSION - 1, NextcloudDatabase.FIRST_ROOM_DB_VERSION) {
 
@@ -50,7 +53,7 @@ class RoomMigration : Migration(NextcloudDatabase.FIRST_ROOM_DB_VERSION - 1, Nex
             "modified_at" to TYPE_INTEGER
         )
 
-        migrateTable(database, "filesystem", newColumns)
+        DatabaseMigrationUtil.migrateTable(database, "filesystem", newColumns)
     }
 
     /**
@@ -76,7 +79,7 @@ class RoomMigration : Migration(NextcloudDatabase.FIRST_ROOM_DB_VERSION - 1, Nex
             "folder_unlock_token" to TYPE_TEXT
         )
 
-        migrateTable(database, "list_of_uploads", newColumns)
+        DatabaseMigrationUtil.migrateTable(database, "list_of_uploads", newColumns)
     }
 
     /**
@@ -134,7 +137,7 @@ class RoomMigration : Migration(NextcloudDatabase.FIRST_ROOM_DB_VERSION - 1, Nex
             "files_locking_version" to TYPE_TEXT
         )
 
-        migrateTable(database, "capabilities", newColumns)
+        DatabaseMigrationUtil.migrateTable(database, "capabilities", newColumns)
     }
 
     /**
@@ -186,52 +189,6 @@ class RoomMigration : Migration(NextcloudDatabase.FIRST_ROOM_DB_VERSION - 1, Nex
             "lock_timeout" to TYPE_INTEGER,
             "lock_token" to TYPE_TEXT
         )
-        migrateTable(database, "filelist", newColumns)
-    }
-
-    private fun migrateTable(database: SupportSQLiteDatabase, tableName: String, newColumns: Map<String, String>) {
-        require(newColumns.isNotEmpty())
-        val newTableTempName = "${tableName}_new"
-        createNewTable(database, newTableTempName, newColumns)
-        copyData(database, tableName, newTableTempName, newColumns.keys)
-        replaceTable(database, tableName, newTableTempName)
-    }
-
-    private fun createNewTable(
-        database: SupportSQLiteDatabase,
-        newTableName: String,
-        columns: Map<String, String>
-    ) {
-        val columnsString = columns.entries.joinToString(",") { "${it.key} ${it.value}" }
-        database.execSQL("CREATE TABLE $newTableName ($columnsString)")
-    }
-
-    private fun copyData(
-        database: SupportSQLiteDatabase,
-        tableName: String,
-        newTableName: String,
-        columnNames: Iterable<String>
-    ) {
-        val columnsString = columnNames.joinToString(",")
-
-        database.execSQL(
-            "INSERT INTO $newTableName ($columnsString) " +
-                "SELECT $columnsString FROM $tableName"
-        )
-    }
-
-    private fun replaceTable(
-        database: SupportSQLiteDatabase,
-        tableName: String,
-        newTableTempName: String
-    ) {
-        database.execSQL("DROP TABLE $tableName")
-        database.execSQL("ALTER TABLE $newTableTempName RENAME TO $tableName")
-    }
-
-    companion object {
-        private const val TYPE_TEXT = "TEXT"
-        private const val TYPE_INTEGER = "INTEGER"
-        private const val TYPE_INTEGER_PRIMARY_KEY = "INTEGER PRIMARY KEY"
+        DatabaseMigrationUtil.migrateTable(database, "filelist", newColumns)
     }
 }