ReadKeyValuesRepo#removeWithValue

This commit is contained in:
InsanusMokrassar 2023-04-25 19:14:38 +06:00
parent 4e97ce86aa
commit 5f231c2212
12 changed files with 147 additions and 3 deletions
CHANGELOG.md
repos
cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache
common/src
commonMain/kotlin/dev/inmo/micro_utils/repos
main/kotlin/dev/inmo/micro_utils/repos/onetomany
exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/onetomany
inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos
ktor
client/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/client/key/values
common/src/commonMain/kotlin/dev/inmo/micro_utils/repos/ktor/common/one_to_many
server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key/values

View File

@ -6,6 +6,8 @@
* `Server`: * `Server`:
* Now it is possible to take query parameters as list * Now it is possible to take query parameters as list
* `Repos`: * `Repos`:
* `Common`:
* New `WriteKeyValuesRepo.removeWithValue`
* `Cache`: * `Cache`:
* Rename full caching factories functions to `fullyCached` * Rename full caching factories functions to `fullyCached`

View File

@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -50,7 +51,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
override suspend fun clearWithValue(v: RegisteredObject) { override suspend fun clearWithValue(v: RegisteredObject) {
originalRepo.clearWithValue(v) originalRepo.clearWithValue(v)
doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) { doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also { kvCache.keys(it).also {
it.results.forEach { id -> it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
@ -73,6 +74,19 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
} }
} }
override suspend fun removeWithValue(v: RegisteredObject) {
originalRepo.removeWithValue(v)
doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also {
it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
kvCache.set(id, it - v)
}
}
}
}
}
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) { override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
originalRepo.add(toAdd) originalRepo.add(toAdd)
toAdd.forEach { (k, v) -> toAdd.forEach { (k, v) ->

View File

@ -157,6 +157,10 @@ open class FullKeyValuesCacheRepo<Key,Value>(
override suspend fun invalidate() { override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo) kvCache.actualizeAll(parentRepo)
} }
override suspend fun removeWithValue(v: Value) {
super<FullWriteKeyValuesCacheRepo>.removeWithValue(v)
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached( fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached(

View File

@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -44,9 +45,23 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
suspend fun add(toAdd: Map<Key, List<Value>>) suspend fun add(toAdd: Map<Key, List<Value>>)
/**
* Removes [Value]s by passed [Key]s without full clear of all data by [Key]
*/
suspend fun remove(toRemove: Map<Key, List<Value>>) suspend fun remove(toRemove: Map<Key, List<Value>>)
/**
* Removes [v] without full clear of all data by [Key]s with [v]
*/
suspend fun removeWithValue(v: Value)
/**
* Fully clear all data by [k]
*/
suspend fun clear(k: Key) suspend fun clear(k: Key)
/**
* Clear [v] **with** full clear of all data by [Key]s with [v]
*/
suspend fun clearWithValue(v: Value) suspend fun clearWithValue(v: Value)
suspend fun set(toSet: Map<Key, List<Value>>) { suspend fun set(toSet: Map<Key, List<Value>>) {
@ -100,6 +115,21 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
keysResult.currentPageIfNotEmpty() keysResult.currentPageIfNotEmpty()
} }
} }
suspend override fun removeWithValue(v: Value) {
val toRemove = mutableMapOf<Key, List<Value>>()
doForAllWithNextPaging {
keys(it).also {
it.results.forEach {
if (contains(it, v)) {
toRemove[it] = listOf(v)
}
}
}
}
remove(toRemove)
}
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>

View File

@ -97,6 +97,8 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
}.toMap() }.toMap()
) )
override suspend fun removeWithValue(v: FromValue) = to.removeWithValue(v.toOutValue())
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) { override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
to.set( to.set(
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap() toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()

View File

@ -4,6 +4,7 @@ import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.KeyValuesRepo import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.set
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@ -60,6 +61,24 @@ open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value
} }
} }
override suspend fun removeWithValue(v: Value) {
val toRemove = mutableMapOf<Key, List<Value>>()
doForAllWithNextPaging {
original.keys(it).also {
it.results.forEach {
val data = original.get(it) ?: return@forEach
if (v in data) {
toRemove[it] = listOf(v)
}
}
}
}
remove(toRemove)
}
override suspend fun add(toAdd: Map<Key, List<Value>>) { override suspend fun add(toAdd: Map<Key, List<Value>>) {
original.set( original.set(
toAdd.mapNotNull { (k, adding) -> toAdd.mapNotNull { (k, adding) ->

View File

@ -6,6 +6,7 @@ import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.reverse import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.crud.asId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@ -260,6 +261,19 @@ class OneToManyAndroidRepo<Key, Value>(
_onValueRemoved.emit(k to v) _onValueRemoved.emit(k to v)
} }
} }
override suspend fun removeWithValue(v: Value) {
helper.blockingWritableTransaction {
val keys = select(tableName, idColumnArray, "$valueColumnName=?", arrayOf(v.valueAsString())).map {
it.asId.keyFromString()
}
keys.filter {
delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(it.keyAsString(), v.valueAsString())) > 0
}
}.forEach { k ->
_onValueRemoved.emit(k to v)
}
}
} }
fun <Key, Value> OneToManyAndroidRepo( fun <Key, Value> OneToManyAndroidRepo(

View File

@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value> typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
@ -66,9 +67,36 @@ open class ExposedKeyValuesRepo<Key, Value>(
} }
} }
override suspend fun removeWithValue(v: Value) {
transaction(database) {
val keys = select { selectByValue(v) }.map { it.asKey }
deleteWhere { SqlExpressionBuilder.selectByValue(v) }
keys
}.forEach {
_onValueRemoved.emit(it to v)
}
}
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
transaction(database) { transaction(database) {
deleteWhere { keyColumn.eq(k) } deleteWhere { keyColumn.eq(k) }
}.also { _onDataCleared.emit(k) } }.also { _onDataCleared.emit(k) }
} }
override suspend fun clearWithValue(v: Value) {
transaction(database) {
val toClear = select { selectByValue(v) }
.asSequence()
.map { it.asKey to it.asObject }
.groupBy { it.first }
.mapValues { it.value.map { it.second } }
deleteWhere { keyColumn.inList(toClear.keys) }
toClear
}.forEach {
it.value.forEach { v ->
_onValueRemoved.emit(it.key to v)
}
_onDataCleared.emit(it.key)
}
}
} }

View File

@ -87,13 +87,27 @@ class MapWriteKeyValuesRepo<Key, Value>(
} }
} }
override suspend fun removeWithValue(v: Value) {
map.forEach { (k, values) ->
if (values.remove(v)) {
_onValueRemoved.emit(k to v)
}
}
}
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
map.remove(k) ?.also { _onDataCleared.emit(k) } map.remove(k) ?.also { _onDataCleared.emit(k) }
} }
override suspend fun clearWithValue(v: Value) { override suspend fun clearWithValue(v: Value) {
map.forEach { (k, values) -> map.filter { (_, values) ->
if (values.remove(v)) _onValueRemoved.emit(k to v) values.contains(v)
}.forEach {
map.remove(it.key) ?.onEach { v ->
_onValueRemoved.emit(it.key to v)
} ?.also { _ ->
_onDataCleared.emit(it.key)
}
} }
} }
} }

View File

@ -47,6 +47,17 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
}.throwOnUnsuccess { "Unable to remove $toRemove" } }.throwOnUnsuccess { "Unable to remove $toRemove" }
} }
@OptIn(InternalAPI::class)
override suspend fun removeWithValue(v: Value) {
httpClient.post(
buildStandardUrl(baseUrl, removeWithValueRoute)
) {
body = v
bodyType = valueTypeInfo
contentType(contentType)
}.throwOnUnsuccess { "Unable to remove $v" }
}
@OptIn(InternalAPI::class) @OptIn(InternalAPI::class)
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
httpClient.post( httpClient.post(

View File

@ -13,6 +13,7 @@ const val onDataClearedRoute = "onDataCleared"
const val addRoute = "add" const val addRoute = "add"
const val removeRoute = "remove" const val removeRoute = "remove"
const val removeWithValueRoute = "removeWithValue"
const val clearRoute = "clear" const val clearRoute = "clear"
const val clearWithValueRoute = "clearWithValue" const val clearWithValueRoute = "clearWithValue"
const val setRoute = "set" const val setRoute = "set"

View File

@ -46,6 +46,11 @@ inline fun <reified Key : Any, reified Value : Any> Route.configureWriteKeyValue
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
post(removeWithValueRoute) {
originalRepo.removeWithValue(call.receive())
call.respond(HttpStatusCode.OK)
}
post(clearRoute) { post(clearRoute) {
originalRepo.clear(call.receive()) originalRepo.clear(call.receive())
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)