diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f9ba5e143e..cb9967a2b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## 0.4.4 +* `Repos`: + * Add interface `VersionsRepo` + * Add default realization of `VersionsRepo` named `StandardVersionsRepo` which use `StandardVersionsRepoProxy` + to get access to some end-store + * Add default realization of `StandardVersionsRepoProxy` based on `KeyValue` repos + * Add realizations of `StandardVersionsRepoProxy` for exposed and android (`SQL` and `SharedPreferences`) + ## 0.4.3 * `Versions`: diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/KeyValueBasedVersionsRepoProxy.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/KeyValueBasedVersionsRepoProxy.kt new file mode 100644 index 00000000000..5d7e3435670 --- /dev/null +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/KeyValueBasedVersionsRepoProxy.kt @@ -0,0 +1,13 @@ +package dev.inmo.micro_utils.repos.versions + +import dev.inmo.micro_utils.repos.StandardKeyValueRepo +import dev.inmo.micro_utils.repos.set + +class KeyValueBasedVersionsRepoProxy( + private val keyValueStore: StandardKeyValueRepo, + override val database: T +) : StandardVersionsRepoProxy { + override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName) + + override suspend fun updateTableVersion(tableName: String, version: Int) { keyValueStore.set(tableName, version) } +} diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/StandardVersionsRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/StandardVersionsRepo.kt new file mode 100644 index 00000000000..b02432c2759 --- /dev/null +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/StandardVersionsRepo.kt @@ -0,0 +1,36 @@ +package dev.inmo.micro_utils.repos.versions + +import dev.inmo.micro_utils.repos.Repo + +interface StandardVersionsRepoProxy : Repo { + val database: T + + suspend fun getTableVersion(tableName: String): Int? + suspend fun updateTableVersion(tableName: String, version: Int) +} + +class StandardVersionsRepo( + private val proxy: StandardVersionsRepoProxy +) : VersionsRepo { + override suspend fun setTableVersion( + tableName: String, + version: Int, + onCreate: suspend T.() -> Unit, + onUpdate: suspend T.(from: Int, to: Int) -> Unit + ) { + var savedVersion = proxy.getTableVersion(tableName) + if (savedVersion == null) { + proxy.database.onCreate() + proxy.updateTableVersion(tableName, version) + } else { + while (savedVersion != null && savedVersion < version) { + val newVersion = savedVersion + 1 + + proxy.database.onUpdate(savedVersion, newVersion) + + proxy.updateTableVersion(tableName, newVersion) + savedVersion = newVersion + } + } + } +} \ No newline at end of file diff --git a/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/VersionsRepo.kt b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/VersionsRepo.kt new file mode 100644 index 00000000000..e026f425924 --- /dev/null +++ b/repos/common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/versions/VersionsRepo.kt @@ -0,0 +1,31 @@ +package dev.inmo.micro_utils.repos.versions + +import dev.inmo.micro_utils.repos.Repo + +/** + * This interface has been created due to requirement to work with different versions of databases and make some + * migrations between versions + * + * @param T It is a type of database, which will be used by this repo to retrieve current table version and update it + */ +interface VersionsRepo : Repo { + /** + * By default, instance of this interface will check that version of table with name [tableName] is less than + * [version] or is absent + * + * * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version] + * * In case if [tableName] have version less than parameter [version], it will increase version one-by-one + * until database version will be equal to [version] + * + * @param version Current version of table + * @param onCreate This callback will be called in case when table have no information about table + * @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter + * "to" will always be greater than "from" + */ + suspend fun setTableVersion( + tableName: String, + version: Int, + onCreate: suspend T.() -> Unit, + onUpdate: suspend T.(from: Int, to: Int) -> Unit + ) +} diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt index 8de4676138a..16cd3d02d58 100644 --- a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt +++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/StandardSQLHelper.kt @@ -5,6 +5,7 @@ import android.database.DatabaseErrorHandler import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import dev.inmo.micro_utils.coroutines.safely +import dev.inmo.micro_utils.repos.versions.* import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -36,6 +37,9 @@ class StandardSQLHelper( override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {} } + val versionsRepo: VersionsRepo by lazy { + StandardVersionsRepo(AndroidSQLStandardVersionsRepoProxy(sqlOpenHelper)) + } suspend fun writableTransaction(block: suspend SQLiteDatabase.() -> T): T = sqlOpenHelper.writableTransaction(block) diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSQLStandardVersionsRepoProxy.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSQLStandardVersionsRepoProxy.kt new file mode 100644 index 00000000000..a0f71c3f993 --- /dev/null +++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSQLStandardVersionsRepoProxy.kt @@ -0,0 +1,63 @@ +package dev.inmo.micro_utils.repos.versions + +import android.database.sqlite.SQLiteOpenHelper +import androidx.core.content.contentValuesOf +import dev.inmo.micro_utils.repos.* +import kotlinx.coroutines.runBlocking + +/** + * Will create [VersionsRepo] based on [SQLiteOpenHelper] with table inside of [database] + */ +@Suppress("NOTHING_TO_INLINE") +inline fun versionsRepo(database: SQLiteOpenHelper): VersionsRepo = StandardVersionsRepo( + AndroidSQLStandardVersionsRepoProxy(database) +) + +class AndroidSQLStandardVersionsRepoProxy( + override val database: SQLiteOpenHelper +) : StandardVersionsRepoProxy { + private val tableName: String = "AndroidSQLStandardVersionsRepo" + private val tableNameColumnName = "tableName" + private val tableVersionColumnName = "version" + + init { + runBlocking(DatabaseCoroutineContext) { + database.writableTransaction { + createTable( + tableName, + tableNameColumnName to ColumnType.Text.NOT_NULLABLE, + tableVersionColumnName to ColumnType.Numeric.INTEGER() + ) + } + } + } + + override suspend fun getTableVersion(tableName: String): Int? = database.writableTransaction { + select( + tableName, + selection = "$tableNameColumnName=?", + selectionArgs = arrayOf(tableName), + limit = limitClause(1) + ).use { + if (it.moveToFirst()) { + it.getInt(tableVersionColumnName) + } else { + null + } + } + } + + override suspend fun updateTableVersion(tableName: String, version: Int) { + database.writableTransaction { + val updated = update( + tableName, + contentValuesOf(tableVersionColumnName to version), + "$tableNameColumnName=?", + arrayOf(version.toString()) + ) > 0 + if (!updated) { + insert(tableName, null, contentValuesOf(tableNameColumnName to tableName, tableVersionColumnName to version)) + } + } + } +} diff --git a/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSharedPreferencesStandardVersionsRepoProxy.kt b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSharedPreferencesStandardVersionsRepoProxy.kt new file mode 100644 index 00000000000..1e9f1e1c1a4 --- /dev/null +++ b/repos/common/src/main/kotlin/dev/inmo/micro_utils/repos/versions/AndroidSharedPreferencesStandardVersionsRepoProxy.kt @@ -0,0 +1,49 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package dev.inmo.micro_utils.repos.versions + +import android.content.Context +import android.database.sqlite.SQLiteOpenHelper +import androidx.core.content.contentValuesOf +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.keyvalue.keyValueStore +import kotlinx.coroutines.runBlocking + +/** + * Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo] + * + * @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo] + * + * @see [KeyValueBasedVersionsRepoProxy] + * @see [keyValueStore] + */ +inline fun Context.versionsKeyValueRepo( + database: T +): VersionsRepo = StandardVersionsRepo( + KeyValueBasedVersionsRepoProxy( + keyValueStore("SPVersionsRepo"), + database + ) +) +/** + * Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo] + * + * @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo] + * + * @see [keyValueStore] + */ +inline fun Context.versionsKeyValueRepoForSQL( + database: SQLiteOpenHelper +) = versionsKeyValueRepo(database) + +/** + * Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo] + * + * @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo] + * + * @see [keyValueStore] + */ +inline fun versionsRepo( + context: Context, + database: SQLiteOpenHelper +) = context.versionsKeyValueRepoForSQL(database) diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/versions/ExposedStandardVersionsRepoProxy.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/versions/ExposedStandardVersionsRepoProxy.kt new file mode 100644 index 00000000000..a561949537e --- /dev/null +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/versions/ExposedStandardVersionsRepoProxy.kt @@ -0,0 +1,48 @@ +package dev.inmo.micro_utils.repos.exposed.versions + +import dev.inmo.micro_utils.repos.exposed.ExposedRepo +import dev.inmo.micro_utils.repos.exposed.initTable +import dev.inmo.micro_utils.repos.versions.* +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction + +/** + * Use this method to create [StandardVersionsRepo] based on [Database] with [ExposedStandardVersionsRepoProxy] as + * [StandardVersionsRepoProxy] + */ +@Suppress("NOTHING_TO_INLINE") +inline fun versionsRepo(database: Database): VersionsRepo = StandardVersionsRepo( + ExposedStandardVersionsRepoProxy(database) +) + +class ExposedStandardVersionsRepoProxy( + override val database: Database +) : StandardVersionsRepoProxy, Table("ExposedVersionsProxy"), ExposedRepo { + private val tableNameColumn = text("tableName") + private val tableVersionColumn = integer("tableName") + + init { + initTable() + } + + override suspend fun getTableVersion(tableName: String): Int? = transaction(database) { + select { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn) + } + + override suspend fun updateTableVersion(tableName: String, version: Int) { + transaction(database) { + val updated = update( + { tableNameColumn.eq(tableName) } + ) { + it[tableVersionColumn] = version + } > 0 + if (!updated) { + insert { + it[tableNameColumn] = tableName + it[tableVersionColumn] = version + } + } + } + } + +}