diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt index 147c00dd7..fb6377c42 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCache.kt @@ -130,7 +130,9 @@ class LargeSoftCache : CacheOperations { to: K, consumer: BiConsumer, ) { - cache.subMap(from, to).forEach(BiConsumerWrapper(this, consumer)) + cache + .subMap(from, true, to, true) + .forEach(BiConsumerWrapper(this, consumer)) } class BiConsumerWrapper( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt index f60d69d8c..5e1e70766 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LargeSoftCacheAddressExt.kt @@ -25,6 +25,9 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.utils.cache.CacheCollectors const val START_KEY = "0000000000000000000000000000000000000000000000000000000000000000" +const val END_KEY = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +const val START_D_TAG = "" +const val END_D_TAG = "\uFFFF\uFFFF\uFFFF\uFFFF" fun kindStart( kind: Int, @@ -34,20 +37,22 @@ fun kindStart( fun kindEnd( kind: Int, pubKey: HexKey, -) = Address(kind + 1, pubKey, "") +) = Address(kind, pubKey, END_D_TAG) fun kindStart(kind: Int) = kindStart(kind, START_KEY) -fun kindEnd(kind: Int) = kindEnd(kind + 1, START_KEY) +fun kindEnd(kind: Int) = kindEnd(kind, END_KEY) + +val ACCEPT_ALL_FILTER = CacheCollectors.BiFilter { key, note -> true } fun LargeSoftCache.filterIntoSet( kind: Int, - consumer: CacheCollectors.BiFilter, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, ): Set = filterIntoSet(kindStart(kind), kindEnd(kind), consumer) fun LargeSoftCache.filterIntoSet( kinds: List, - consumer: CacheCollectors.BiFilter, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, ): Set { val set = mutableSetOf() kinds.forEach { @@ -59,7 +64,7 @@ fun LargeSoftCache.filterIntoSet( fun LargeSoftCache.filterIntoSet( kind: Int, pubKey: HexKey, - consumer: CacheCollectors.BiFilter, + consumer: CacheCollectors.BiFilter = ACCEPT_ALL_FILTER, ): Set = filterIntoSet(kindStart(kind, pubKey), kindEnd(kind, pubKey), consumer) fun LargeSoftCache.mapNotNullIntoSet( diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt new file mode 100644 index 000000000..56df541b3 --- /dev/null +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/model/LargeCacheAddressableFilterTest.kt @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.model + +import com.vitorpamplona.quartz.nip01Core.core.Address +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class LargeCacheAddressableFilterTest { + companion object { + fun LargeSoftCache.addMock( + kind: Int, + pubkey: HexKey, + dTag: String = "", + ) { + val address = Address(kind, pubkey, dTag) + put(address, AddressableNote(address)) + } + + val cache = + LargeSoftCache().apply { + addMock(32000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32000, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32000, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "a") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "z") + addMock(32001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad", "askldfjaljksdflkaj") + addMock(32001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32001, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32001, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32002, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32002, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + + addMock(32003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + addMock(32003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + addMock(32003, "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab") + addMock(32003, "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a") + } + } + + @Test + fun filterIntoSet() { + val query0 = cache.filterIntoSet(32_000) + val query1 = cache.filterIntoSet(32_001) + val query2 = cache.filterIntoSet(32_002) + val query3 = cache.filterIntoSet(32_003) + + assertEquals(4, query0.size) + assertEquals(7, query1.size) + assertEquals(4, query2.size) + assertEquals(4, query3.size) + } + + @Test + fun mapIntoSet() { + val query0 = cache.mapNotNullIntoSet(32_000) { key, note -> note } + val query1 = cache.mapNotNullIntoSet(32_001) { key, note -> note } + val query2 = cache.mapNotNullIntoSet(32_002) { key, note -> note } + val query3 = cache.mapNotNullIntoSet(32_003) { key, note -> note } + + assertEquals(4, query0.size) + assertEquals(7, query1.size) + assertEquals(4, query2.size) + assertEquals(4, query3.size) + } + + @Test + fun filterListIntoSet() { + val query = cache.filterIntoSet(listOf(32_000, 32_001, 32_002, 32_003)) + + assertEquals(19, query.size) + + val query2 = cache.filterIntoSet(listOf(32_000, 32_002, 32_003)) + + assertEquals(12, query2.size) + + val query3 = cache.filterIntoSet(listOf(32_000, 32_001, 32_003)) + + assertEquals(15, query3.size) + } + + @Test + fun filterKindKeyIntoSet() { + val query0 = cache.filterIntoSet(32_000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query1 = cache.filterIntoSet(32_001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query2 = cache.filterIntoSet(32_002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + val query3 = cache.filterIntoSet(32_003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") + + assertEquals(1, query0.size) + assertEquals(1, query1.size) + assertEquals(1, query2.size) + assertEquals(1, query3.size) + + val query4 = cache.filterIntoSet(32_000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query5 = cache.filterIntoSet(32_001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query6 = cache.filterIntoSet(32_002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + val query7 = cache.filterIntoSet(32_003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") + + assertEquals(1, query4.size) + assertEquals(4, query5.size) + assertEquals(1, query6.size) + assertEquals(1, query7.size) + } + + @Test + fun mapKindKeyIntoSet() { + val query0 = cache.mapNotNullIntoSet(32_000, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query1 = cache.mapNotNullIntoSet(32_001, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query2 = cache.mapNotNullIntoSet(32_002, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + val query3 = cache.mapNotNullIntoSet(32_003, "6dd3b72e325da7383b275eef1c66131ba4664326e162bc060527509b4e33ae43") { key, note -> note } + + assertEquals(1, query0.size) + assertEquals(1, query1.size) + assertEquals(1, query2.size) + assertEquals(1, query3.size) + + val query4 = cache.mapNotNullIntoSet(32_000, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query5 = cache.mapNotNullIntoSet(32_001, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query6 = cache.mapNotNullIntoSet(32_002, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + val query7 = cache.mapNotNullIntoSet(32_003, "0b6d941c46411a95edb1c93da7ad6ca26370497d8c7b7d621f5cb59f48841bad") { key, note -> note } + + assertEquals(1, query4.size) + assertEquals(4, query5.size) + assertEquals(1, query6.size) + assertEquals(1, query7.size) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt index 96a18f520..10f9bcc40 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.kt @@ -92,6 +92,104 @@ expect class LargeCache() : ICacheOperations { override fun associateWith(transform: (K, V) -> U?): Map + override fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List + + override fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set + + override fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List + + override fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List + + override fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set + + override fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List + + override fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set + + override fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? + + override fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int + + override fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long + + override fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> + + override fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map + + override fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map + + override fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int + + override fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map + + override fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map + override fun joinToString( separator: CharSequence, prefix: CharSequence, diff --git a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt index 6d5910ad7..91ab979bf 100644 --- a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt +++ b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt @@ -147,6 +147,136 @@ actual class LargeCache : ICacheOperations { TODO("Not yet implemented") } + actual override fun filter( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): List { + TODO("Not yet implemented") + } + + actual override fun filterIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Set { + TODO("Not yet implemented") + } + + actual override fun map( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapNotNull( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapNotNullIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper, + ): Set { + TODO("Not yet implemented") + } + + actual override fun mapFlatten( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): List { + TODO("Not yet implemented") + } + + actual override fun mapFlattenIntoSet( + from: K, + to: K, + consumer: CacheCollectors.BiMapper?>, + ): Set { + TODO("Not yet implemented") + } + + actual override fun maxOrNullOf( + from: K, + to: K, + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? { + TODO("Not yet implemented") + } + + actual override fun sumOf( + from: K, + to: K, + consumer: CacheCollectors.BiSumOf, + ): Int { + TODO("Not yet implemented") + } + + actual override fun sumOfLong( + from: K, + to: K, + consumer: CacheCollectors.BiSumOfLong, + ): Long { + TODO("Not yet implemented") + } + + actual override fun groupBy( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map> { + TODO("Not yet implemented") + } + + actual override fun countByGroup( + from: K, + to: K, + consumer: CacheCollectors.BiNotNullMapper, + ): Map { + TODO("Not yet implemented") + } + + actual override fun sumByGroup( + from: K, + to: K, + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map { + TODO("Not yet implemented") + } + + actual override fun count( + from: K, + to: K, + consumer: CacheCollectors.BiFilter, + ): Int { + TODO("Not yet implemented") + } + + actual override fun associate( + from: K, + to: K, + transform: (K, V) -> Pair, + ): Map { + TODO("Not yet implemented") + } + + actual override fun associateWith( + from: K, + to: K, + transform: (K, V) -> U?, + ): Map { + TODO("Not yet implemented") + } + actual override fun joinToString( separator: CharSequence, prefix: CharSequence, diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt index c175ada41..40819f546 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.jvmAndroid.kt @@ -85,6 +85,8 @@ actual class LargeCache : CacheOperations { to: K, consumer: BiConsumer, ) { - cache.subMap(from, to).forEach(consumer) + cache + .subMap(from, true, to, true) + .forEach(consumer) } }