Можно ли загрузить предварительно заполненную базу данных из локального ресурса с помощью sqldelight

У меня относительно большая база данных, инициализация которой может занять от 1 до 2 минут. Можно ли загрузить предварительно заполненную базу данных при использовании sqldelight (мультиплатформа kotlin) вместо инициализации базы данных при запуске приложения?

4 ответа

Извините, но я пока не могу добавлять комментарии, что было бы более уместно ...


Хотя вы не отвечаете на ваш вопрос напрямую, 1-2 минуты - это действительно много для sqlite. Что делаешь? Я бы сначала убедился, что вы правильно используете транзакции. 1-2 минуты вставки данных (возможно) приведут к огромному файлу db.

В качестве альтернативы моя проблема, из-за которой мне пришлось использовать предварительно заполненную базу данных, была связана с большим размером файлов .sq (более 30 МБ текста INSERT на таблицу), и SqlDeLight молча прервал генерацию, не отображая сообщения об ошибках.


Вам нужно будет поместить файл db в активы на Android и в пакет на iOS и скопировать их в соответствующие папки перед инициализацией sqldelight.

Необходимость загружать базу данных из ресурсов как на Android, так и на iOS - это большая работа + это означает, что общий проект не будет единственным местом, где инициализируются данные.

Многоплатформенная библиотека Kotlin Moko-resources решает проблему единого источника для базы данных в общем модуле. Это работает для KMM одинаково для Android и iOS.

К сожалению, использование этой возможности практически не представлено в образцах библиотеки. Я добавил второй метод (getDriver) к ожидаемому классу DatabaseDriverFactory, чтобы открыть подготовленную базу данных, и реализовал его на платформе. Например, для androidMain:

      actual class DatabaseDriverFactory(private val context: Context) {
    actual fun createDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
        return AndroidSqliteDriver(schema, context, fileName)
    }

    actual fun getDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
        val database: File = context.getDatabasePath(fileName)

        if (!database.exists()) {
            val inputStream = context.resources.openRawResource(MR.files.dbfile.rawResId)
            val outputStream = FileOutputStream(database.absolutePath)

            inputStream.use { input: InputStream ->
                outputStream.use { output: FileOutputStream ->
                    input.copyTo(output)
                }
            }
        }

        return AndroidSqliteDriver(schema, context, fileName)
    }
}

MR.files.fullDb - это FileResource из класса, сгенерированного библиотекой, он связан с именем файла, расположенного в каталоге resources / MR / files модуля commonMain. Свойство rawResId представляет идентификатор ресурса на стороне платформы.

Я преобразовал код @Denis Luttcev в целевую сторону ios, и он отлично работает в моем мультиплатформенном проекте Kotlin. У меня были некоторые проблемы с выяснением того, как заставить это работать, поэтому, если кому-то это понадобится, вот что я получил:

      actual class DriverFactory {
    actual fun createDriver(schema: SqlDriver.Schema, fileName: String): SqlDriver {
        val fileManager = NSFileManager.defaultManager
        val documentsPath = NSSearchPathForDirectoriesInDomains(
            directory = NSApplicationSupportDirectory,
            domainMask = NSUserDomainMask,
            expandTilde = true
        ).first() as NSString

        val dbDirectoryPath = documentsPath.stringByAppendingPathComponent("databases")
        val targetDbPath = documentsPath.stringByAppendingPathComponent("databases/$fileName")
        val sourceDbPath = MR.files.dbfile.path

        val directoryExists = fileManager.fileExistsAtPath(dbDirectoryPath)
        val databaseExists = fileManager.fileExistsAtPath(targetDbPath)

        if (databaseExists.not()) {
            memScoped {
                // you can use it to log errors
                val error: ObjCObjectVar<NSError?> = alloc()

                if (directoryExists.not()) {
                    val createSuccess = fileManager.createDirectoryAtPath(
                        path = dbDirectoryPath,
                        withIntermediateDirectories = true,
                        attributes = null,
                        error = error.ptr
                    )
                }

                val copySuccess = fileManager.copyItemAtPath(
                    srcPath = sourceDbPath,
                    toPath = targetDbPath,
                    error = error.ptr
                )
            }
        }

        return NativeSqliteDriver(schema, fileName)
    }
}

Да, но это может быть сложно. Не только для "Мультиплатформен". Вам нужно скопировать db в папку db перед попыткой инициализации sqldelight. Это, вероятно, означает, что ввод / вывод в главном потоке при запуске приложения.

Нет стандартного способа сделать это сейчас. Вам нужно будет поместить файл db в ресурсы на Android и в связку на iOS и скопировать их в соответствующие папки перед инициализацией sqldelight. Очевидно, вы захотите проверить, существует ли сначала БД, или иметь какой-то способ узнать, что это ваш первый запуск приложения.

Если вы планируете отправлять обновления, которые будут иметь более новые базы данных, вам нужно будет управлять версиями за пределами проверки наличия базы данных.

Хотя и не отвечая непосредственно на ваш вопрос, от 1 до 2 минут очень, очень долго для sqlite. Что делаешь? Сначала я должен убедиться, что вы используете транзакции правильно. 1-2 минуты вставки данных (вероятно) приведут к огромному файлу БД.

Единственное, что вам нужно, это указать путь к файлу БД с помощью драйвера.

Предположим, что ваша БД находится в /mnt/my_best_app_dbs/super.db. Теперь пройдите путь в nameсобственность Водителя. Что-то вроде этого:

      val sqlDriver: SqlDriver = AndroidSqliteDriver(Schema, context, "/mnt/my_best_app_dbs/best.db")

Имейте в виду, что вам могут потребоваться разрешения, позволяющие читать данный тип хранилища.

Другие вопросы по тегам