From 120e7228c78ec1c302d8cd766016c022468a620a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 2 Aug 2024 22:17:52 +0600 Subject: [PATCH] add FullKeyValuesCacheRepoTests and small rewrite of MapWriteKeyValuesRepo.set(Map>) function --- .../repos/cache/full/FullKeyValueCacheRepo.kt | 22 ++++ .../cache/full/FullKeyValuesCacheRepo.kt | 101 ++++++++++++++++-- .../full/FullKeyValuesCacheRepoTests.kt | 56 ++++++++++ .../micro_utils/repos/MapKeyValuesRepo.kt | 13 +++ 4 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 repos/cache/src/commonTest/kotlin/full/FullKeyValuesCacheRepoTests.kt diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt index 86140ebe27f..0eeae1cba92 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt @@ -154,6 +154,28 @@ open class FullKeyValueCacheRepo( parentRepo.clear() kvCache.clear() } + + override suspend fun set(toSet: Map) { + locker.withWriteLock { + super.set(toSet) + kvCache.set( + toSet.filter { + parentRepo.contains(it.key) + } + ) + } + } + + override suspend fun unset(toUnset: List) { + locker.withWriteLock { + super.unset(toUnset) + kvCache.unset( + toUnset.filter { + !parentRepo.contains(it) + } + ) + } + } } fun KeyValueRepo.fullyCached( diff --git a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt index 80844c4d097..aea3163ed0a 100644 --- a/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt +++ b/repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt @@ -65,6 +65,42 @@ open class FullReadKeyValuesCacheRepo( ) } + override suspend fun getAll(k: Key, reversed: Boolean): List { + return doOrTakeAndActualizeWithWriteLock( + { + get(k) ?.optionallyReverse(reversed).optionalOrAbsentIfNull + }, + { getAll(k, reversed) }, + { kvCache.set(k, it.optionallyReverse(reversed)) } + ) + } + + override suspend fun getAll(reverseLists: Boolean): Map> { + return doOrTakeAndActualizeWithWriteLock( + { + getAll().takeIf { it.isNotEmpty() } ?.let { + if (reverseLists) { + it.mapValues { it.value.reversed() } + } else { + it + } + }.optionalOrAbsentIfNull + }, + { getAll(reverseLists) }, + { + kvCache.set( + it.let { + if (reverseLists) { + it.mapValues { it.value.reversed() } + } else { + it + } + } + ) + } + ) + } + override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult { return doOrTakeAndActualize( { @@ -163,14 +199,15 @@ fun WriteKeyValuesRepo.caching( ) = FullWriteKeyValuesCacheRepo(this, kvCache, scope, locker) open class FullKeyValuesCacheRepo( - protected open val parentRepo: KeyValuesRepo, + override val parentRepo: KeyValuesRepo, kvCache: KeyValueRepo>, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), skipStartInvalidate: Boolean = false, locker: SmartRWLocker = SmartRWLocker(), -) : FullWriteKeyValuesCacheRepo(parentRepo, kvCache, scope, locker), +) : //FullWriteKeyValuesCacheRepo(parentRepo, kvCache, scope, locker), KeyValuesRepo, - ReadKeyValuesRepo by FullReadKeyValuesCacheRepo(parentRepo, kvCache, locker) { + FullReadKeyValuesCacheRepo(parentRepo, kvCache, locker), + WriteKeyValuesRepo by parentRepo { init { if (!skipStartInvalidate) { scope.launchSafelyWithoutExceptions { invalidate() } @@ -190,11 +227,63 @@ open class FullKeyValuesCacheRepo( } override suspend fun set(toSet: Map>) { - super.set(toSet) + locker.withWriteLock { + parentRepo.set(toSet) + kvCache.set( + toSet.filter { + parentRepo.contains(it.key) + } + ) + } } - override suspend fun removeWithValue(v: Value) { - super.removeWithValue(v) + override suspend fun add(toAdd: Map>) { + locker.withWriteLock { + parentRepo.add(toAdd) + toAdd.forEach { + val filtered = it.value.filter { v -> + parentRepo.contains(it.key, v) + }.ifEmpty { + return@forEach + } + kvCache.set( + it.key, + (kvCache.get(it.key) ?: emptyList()) + filtered + ) + } + } + } + + override suspend fun remove(toRemove: Map>) { + locker.withWriteLock { + parentRepo.remove(toRemove) + toRemove.forEach { + val filtered = it.value.filter { v -> + !parentRepo.contains(it.key, v) + }.ifEmpty { + return@forEach + }.toSet() + val resultList = (kvCache.get(it.key) ?: emptyList()) - filtered + if (resultList.isEmpty()) { + kvCache.unset(it.key) + } else { + kvCache.set( + it.key, + resultList + ) + } + } + } + } + + override suspend fun clear(k: Key) { + locker.withWriteLock { + parentRepo.clear(k) + if (parentRepo.contains(k)) { + return@withWriteLock + } + kvCache.unset(k) + } } } diff --git a/repos/cache/src/commonTest/kotlin/full/FullKeyValuesCacheRepoTests.kt b/repos/cache/src/commonTest/kotlin/full/FullKeyValuesCacheRepoTests.kt new file mode 100644 index 00000000000..ce37c92233d --- /dev/null +++ b/repos/cache/src/commonTest/kotlin/full/FullKeyValuesCacheRepoTests.kt @@ -0,0 +1,56 @@ +package full + +import com.benasher44.uuid.uuid4 +import dev.inmo.micro_utils.repos.* +import dev.inmo.micro_utils.repos.cache.full.FullKeyValuesCacheRepo +import korlibs.time.days +import korlibs.time.years +import kotlinx.coroutines.test.runTest +import kotlin.test.* + +class FullKeyValuesCacheRepoTests { + @Test + fun creatingWorksProperly() = runTest(timeout = 1.days) { + val testData = (0 until 1000).associate { + (it.toString() + uuid4().toString()) to (0 until 1000).map { + uuid4().toString() + }.sorted() + } + val updatedTestData = testData.keys.associateWith { + (0 until 1000).map { + uuid4().toString() + }.sorted() + } + val addedData = testData.keys.associateWith { + uuid4().toString() + } + val kvCache = MapKeyValueRepo>() + val kvRepo = MapKeyValuesRepo() + + val cacheRepo = FullKeyValuesCacheRepo( + kvRepo, + kvCache + ) + + testData.forEach { + cacheRepo.set(it.key, it.value) + assertContentEquals(it.value, cacheRepo.getAll(it.key)) + assertContentEquals(it.value, kvRepo.getAll(it.key)) + assertContentEquals(it.value, kvCache.get(it.key) ?.sorted()) + } + + updatedTestData.forEach { + cacheRepo.set(it.key, it.value) + assertContentEquals(it.value, cacheRepo.getAll(it.key)) + assertContentEquals(it.value, kvRepo.getAll(it.key)) + assertContentEquals(it.value, kvCache.get(it.key) ?.sorted()) + } + + addedData.forEach { + cacheRepo.add(it.key, it.value) + assertTrue(cacheRepo.contains(it.key, it.value)) + assertTrue(kvRepo.contains(it.key, it.value)) + assertTrue(kvCache.get(it.key) !!.contains(it.value)) + } + } +} diff --git a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt index 226bb93a36d..fb9bc5d8a7f 100644 --- a/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt +++ b/repos/inmemory/src/commonMain/kotlin/dev/inmo/micro_utils/repos/MapKeyValuesRepo.kt @@ -108,6 +108,19 @@ class MapWriteKeyValuesRepo( } } + override suspend fun set(toSet: Map>) { + locker.withWriteLock { + toSet.forEach { + map[it.key] = it.value.toMutableList() + } + } + toSet.forEach { (k, v) -> + v.forEach { + _onNewValue.emit(k to it) + } + } + } + override suspend fun remove(toRemove: Map>) { val removed = mutableListOf>() val cleared = mutableListOf()