diff --git a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java index 09613f83b..a533e9483 100644 --- a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java +++ b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java @@ -667,18 +667,18 @@ public class RedissonLocalCachedMap extends RedissonMap implements R } @Override - public Set keySet() { - return new KeySet(); + public Set keySet(String pattern) { + return new KeySet(pattern); } @Override - public Collection values() { - return new Values(); + public Collection values(String keyPattern) { + return new Values(keyPattern); } @Override - public Set> entrySet() { - return new EntrySet(); + public Set> entrySet(String keyPattern) { + return new EntrySet(keyPattern); } private Iterator> cacheEntrySetIterator() { @@ -732,9 +732,15 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final class KeySet extends AbstractSet { + private final String pattern; + + public KeySet(String pattern) { + this.pattern = pattern; + } + @Override public Iterator iterator() { - return new CompositeIterable(cacheKeySetIterator(), RedissonLocalCachedMap.super.keySet().iterator()) { + return new CompositeIterable(cacheKeySetIterator(), RedissonLocalCachedMap.super.keySet(pattern).iterator()) { @Override boolean isCacheContains(Object object) { @@ -769,9 +775,15 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final class Values extends AbstractCollection { + private final String keyPattern; + + public Values(String keyPattern) { + this.keyPattern = keyPattern; + } + @Override public Iterator iterator() { - final Iterator> iter = RedissonLocalCachedMap.this.entrySet().iterator(); + final Iterator> iter = RedissonLocalCachedMap.this.entrySet(keyPattern).iterator(); return new Iterator() { @Override @@ -810,8 +822,14 @@ public class RedissonLocalCachedMap extends RedissonMap implements R final class EntrySet extends AbstractSet> { + private final String keyPattern; + + public EntrySet(String keyPattern) { + this.keyPattern = keyPattern; + } + public final Iterator> iterator() { - return new CompositeIterable>(cacheEntrySetIterator(), RedissonLocalCachedMap.super.entrySet().iterator()) { + return new CompositeIterable>(cacheEntrySetIterator(), RedissonLocalCachedMap.super.entrySet(keyPattern).iterator()) { @Override boolean isCacheContains(Map.Entry entry) { diff --git a/redisson/src/main/java/org/redisson/RedissonMap.java b/redisson/src/main/java/org/redisson/RedissonMap.java index 47638bcd4..7c4f7fd22 100644 --- a/redisson/src/main/java/org/redisson/RedissonMap.java +++ b/redisson/src/main/java/org/redisson/RedissonMap.java @@ -15,7 +15,6 @@ */ package org.redisson; -import java.io.IOException; import java.math.BigDecimal; import java.net.InetSocketAddress; import java.util.AbstractCollection; @@ -322,17 +321,29 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public Set keySet() { - return new KeySet(); + return keySet(null); + } + + public Set keySet(String pattern) { + return new KeySet(pattern); } @Override public Collection values() { - return new Values(); + return values(null); } + public Collection values(String keyPattern) { + return new Values(keyPattern); + } + @Override public Set> entrySet() { - return new EntrySet(); + return entrySet(null); + } + + public Set> entrySet(String keyPattern) { + return new EntrySet(keyPattern); } @Override @@ -938,9 +949,14 @@ public class RedissonMap extends RedissonExpirable implements RMap { return get(fastRemoveAsync(keys)); } - MapScanResult scanIterator(String name, InetSocketAddress client, long startPos) { + MapScanResult scanIterator(String name, InetSocketAddress client, long startPos, String pattern) { + if (pattern == null) { + RFuture> f + = commandExecutor.readAsync(client, name, new MapScanCodec(codec), RedisCommands.HSCAN, name, startPos); + return get(f); + } RFuture> f - = commandExecutor.readAsync(client, name, new MapScanCodec(codec), RedisCommands.HSCAN, name, startPos); + = commandExecutor.readAsync(client, name, new MapScanCodec(codec), RedisCommands.HSCAN, name, startPos, "MATCH", pattern); return get(f); } @@ -1020,8 +1036,8 @@ public class RedissonMap extends RedissonExpirable implements RMap { return h; } - protected Iterator keyIterator() { - return new RedissonMapIterator(RedissonMap.this) { + protected Iterator keyIterator(String pattern) { + return new RedissonMapIterator(RedissonMap.this, pattern) { @Override protected K getValue(java.util.Map.Entry entry) { return (K) entry.getKey().getObj(); @@ -1029,11 +1045,17 @@ public class RedissonMap extends RedissonExpirable implements RMap { }; } - final class KeySet extends AbstractSet { + class KeySet extends AbstractSet { + + private final String pattern; + + public KeySet(String pattern) { + this.pattern = pattern; + } @Override public Iterator iterator() { - return keyIterator(); + return keyIterator(pattern); } @Override @@ -1048,6 +1070,13 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public int size() { + if (pattern != null) { + int size = 0; + for (K val : this) { + size++; + } + return size; + } return RedissonMap.this.size(); } @@ -1058,8 +1087,8 @@ public class RedissonMap extends RedissonExpirable implements RMap { } - protected Iterator valueIterator() { - return new RedissonMapIterator(RedissonMap.this) { + protected Iterator valueIterator(String pattern) { + return new RedissonMapIterator(RedissonMap.this, pattern) { @Override protected V getValue(java.util.Map.Entry entry) { return (V) entry.getValue().getObj(); @@ -1069,9 +1098,15 @@ public class RedissonMap extends RedissonExpirable implements RMap { final class Values extends AbstractCollection { + private final String keyPattern; + + public Values(String keyPattern) { + this.keyPattern = keyPattern; + } + @Override public Iterator iterator() { - return valueIterator(); + return valueIterator(keyPattern); } @Override @@ -1081,6 +1116,14 @@ public class RedissonMap extends RedissonExpirable implements RMap { @Override public int size() { + if (keyPattern != null) { + int size = 0; + for (V val : this) { + size++; + } + return size; + } + return RedissonMap.this.size(); } @@ -1091,8 +1134,8 @@ public class RedissonMap extends RedissonExpirable implements RMap { } - protected Iterator> entryIterator() { - return new RedissonMapIterator>(RedissonMap.this); + protected Iterator> entryIterator(String pattern) { + return new RedissonMapIterator>(RedissonMap.this, pattern); } private void loadValue(final K key, final RPromise result, final boolean replaceValue) { @@ -1179,8 +1222,14 @@ public class RedissonMap extends RedissonExpirable implements RMap { final class EntrySet extends AbstractSet> { + private final String keyPattern; + + public EntrySet(String keyPattern) { + this.keyPattern = keyPattern; + } + public final Iterator> iterator() { - return entryIterator(); + return entryIterator(keyPattern); } public final boolean contains(Object o) { @@ -1203,6 +1252,14 @@ public class RedissonMap extends RedissonExpirable implements RMap { } public final int size() { + if (keyPattern != null) { + int size = 0; + for (Entry val : this) { + size++; + } + return size; + } + return RedissonMap.this.size(); } diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java index 583a513e3..c664fed29 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapCache.java +++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java @@ -922,17 +922,29 @@ public class RedissonMapCache extends RedissonMap implements RMapCac } @Override - MapScanResult scanIterator(String name, InetSocketAddress client, long startPos) { - return get(scanIteratorAsync(name, client, startPos)); + MapScanResult scanIterator(String name, InetSocketAddress client, long startPos, String pattern) { + return get(scanIteratorAsync(name, client, startPos, pattern)); } - public RFuture> scanIteratorAsync(final String name, InetSocketAddress client, long startPos) { + public RFuture> scanIteratorAsync(final String name, InetSocketAddress client, long startPos, String pattern) { + List params = new ArrayList(); + params.add(System.currentTimeMillis()); + params.add(startPos); + if (pattern != null) { + params.add(pattern); + } + RedisCommand> EVAL_HSCAN = new RedisCommand>("EVAL", new ListMultiDecoder(new LongMultiDecoder(), new ObjectMapDecoder(new MapScanCodec(codec)), new ObjectListDecoder(codec), new MapCacheScanResultReplayDecoder()), ValueType.MAP); RFuture> f = commandExecutor.evalReadAsync(client, name, codec, EVAL_HSCAN, "local result = {}; " + "local idleKeys = {}; " - + "local res = redis.call('hscan', KEYS[1], ARGV[2]); " + + "local res; " + + "if (#ARGV == 3) then " + + " res = redis.call('hscan', KEYS[1], ARGV[2], 'match', ARGV[3]); " + + "else " + + " res = redis.call('hscan', KEYS[1], ARGV[2]); " + + "end;" + "local currentTime = tonumber(ARGV[1]); " + "for i, value in ipairs(res[2]) do " + "if i % 2 == 0 then " @@ -960,7 +972,9 @@ public class RedissonMapCache extends RedissonMap implements RMapCac + "end; " + "end; " + "end;" - + "return {res[1], result, idleKeys};", Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), System.currentTimeMillis(), startPos); + + "return {res[1], result, idleKeys};", + Arrays.asList(name, getTimeoutSetName(name), getIdleSetName(name)), + params.toArray()); f.addListener(new FutureListener>() { @Override diff --git a/redisson/src/main/java/org/redisson/RedissonMapIterator.java b/redisson/src/main/java/org/redisson/RedissonMapIterator.java index 63415cdec..a6794fe0c 100644 --- a/redisson/src/main/java/org/redisson/RedissonMapIterator.java +++ b/redisson/src/main/java/org/redisson/RedissonMapIterator.java @@ -20,16 +20,26 @@ import java.util.Map.Entry; import org.redisson.client.protocol.decoder.MapScanResult; import org.redisson.client.protocol.decoder.ScanObjectEntry; +/** + * + * @author Nikita Koksharov + * + * @param key type + * @param value type + * @param loaded value type + */ public class RedissonMapIterator extends RedissonBaseMapIterator { private final RedissonMap map; + private final String pattern; - public RedissonMapIterator(RedissonMap map) { + public RedissonMapIterator(RedissonMap map, String pattern) { this.map = map; + this.pattern = pattern; } protected MapScanResult iterator() { - return map.scanIterator(map.getName(), client, nextIterPos); + return map.scanIterator(map.getName(), client, nextIterPos, pattern); } protected void removeKey() { diff --git a/redisson/src/main/java/org/redisson/api/RKeys.java b/redisson/src/main/java/org/redisson/api/RKeys.java index 56bd641b6..69f8692a9 100644 --- a/redisson/src/main/java/org/redisson/api/RKeys.java +++ b/redisson/src/main/java/org/redisson/api/RKeys.java @@ -187,7 +187,7 @@ public interface RKeys extends RKeysAsync { String randomKey(); /** - * Find keys by key search pattern at once + * Find keys by key search pattern at once using KEYS command. * * Supported glob-style patterns: * h?llo subscribes to hello, hallo and hxllo diff --git a/redisson/src/main/java/org/redisson/api/RMap.java b/redisson/src/main/java/org/redisson/api/RMap.java index 0ddf69931..28aa91954 100644 --- a/redisson/src/main/java/org/redisson/api/RMap.java +++ b/redisson/src/main/java/org/redisson/api/RMap.java @@ -291,22 +291,80 @@ public interface RMap extends ConcurrentMap, RExpirable, RMapAsyncDOESN'T fetch all of them as {@link #readAllKeySet()} does. + * + * @return key set */ @Override Set keySet(); /** - * Returns values collections. + * Returns key set matches pattern. + * This method DOESN'T fetch all of them as {@link #readAllKeySet()} does. + * + * Supported glob-style patterns: + *

+ * h?llo subscribes to hello, hallo and hxllo + *

+ * h*llo subscribes to hllo and heeeello + *

+ * h[ae]llo subscribes to hello and hallo, but not hillo + * + * @param pattern - key pattern + * @return key set + */ + Set keySet(String pattern); + + + /** + * Returns values collection. * This method DOESN'T fetch all of them as {@link #readAllValues()} does. + * + * @return value collection */ @Override Collection values(); /** - * Returns values collections. + * Returns values collection matches key pattern. + * This method DOESN'T fetch all of them as {@link #readAllValues()} does. + * + * Supported glob-style patterns: + *

+ * h?llo subscribes to hello, hallo and hxllo + *

+ * h*llo subscribes to hllo and heeeello + *

+ * h[ae]llo subscribes to hello and hallo, but not hillo + * + * @param keyPattern - key pattern + * @return value collection + */ + Collection values(String keyPattern); + + /** + * Returns map entries collection. * This method DOESN'T fetch all of them as {@link #readAllEntrySet()} does. + * + * @return map entries collection */ @Override Set> entrySet(); + + /** + * Returns map entries collection matches key pattern. + * This method DOESN'T fetch all of them as {@link #readAllEntrySet()} does. + * + * Supported glob-style patterns: + *

+ * h?llo subscribes to hello, hallo and hxllo + *

+ * h*llo subscribes to hllo and heeeello + *

+ * h[ae]llo subscribes to hello and hallo, but not hillo + * + * @param keyPattern - key pattern + * @return map entries collection + */ + Set> entrySet(String keyPattern); } diff --git a/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java b/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java index e247dcff8..540865835 100644 --- a/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java +++ b/redisson/src/main/java/org/redisson/reactive/RedissonMapCacheReactive.java @@ -168,7 +168,7 @@ public class RedissonMapCacheReactive extends RedissonExpirableReactive im return reactive(new Supplier>>() { @Override public RFuture> get() { - return ((RedissonMapCache)mapCache).scanIteratorAsync(getName(), client, startPos); + return ((RedissonMapCache)mapCache).scanIteratorAsync(getName(), client, startPos, null); } }); } diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java index 89e3a4d4c..b6fbecb57 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java @@ -3,6 +3,7 @@ package org.redisson; import static org.assertj.core.api.Assertions.assertThat; import java.io.Serializable; +import java.util.AbstractMap; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -30,6 +31,7 @@ import org.redisson.api.map.event.EntryRemovedListener; import org.redisson.api.map.event.EntryUpdatedListener; import org.redisson.client.codec.DoubleCodec; import org.redisson.client.codec.LongCodec; +import org.redisson.client.codec.StringCodec; import org.redisson.codec.JsonJacksonCodec; import org.redisson.codec.MsgPackJacksonCodec; @@ -495,6 +497,43 @@ public class RedissonMapCacheTest extends BaseMapTest { Assert.assertTrue(map.values().contains(new SimpleValue("2"))); } + @Test + public void testKeySetByPattern() { + RMapCache map = redisson.getMapCache("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200", 1, TimeUnit.MINUTES); + map.put("30", "300"); + + assertThat(map.keySet("?0")).containsExactly("10", "20", "30"); + assertThat(map.keySet("1")).isEmpty(); + assertThat(map.keySet("10")).containsExactly("10"); + } + + @Test + public void testValuesByPattern() { + RMapCache map = redisson.getMapCache("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200", 1, TimeUnit.MINUTES); + map.put("30", "300"); + + assertThat(map.values("?0")).containsExactly("100", "200", "300"); + assertThat(map.values("1")).isEmpty(); + assertThat(map.values("10")).containsExactly("100"); + } + + @Test + public void testEntrySetByPattern() { + RMapCache map = redisson.getMapCache("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200", 1, TimeUnit.MINUTES); + map.put("30", "300"); + + assertThat(map.entrySet("?0")).containsExactly(new AbstractMap.SimpleEntry("10", "100"), new AbstractMap.SimpleEntry("20", "200"), new AbstractMap.SimpleEntry("30", "300")); + assertThat(map.entrySet("1")).isEmpty(); + assertThat(map.entrySet("10")).containsExactly(new AbstractMap.SimpleEntry("10", "100")); + } + + @Test public void testContainsValue() throws InterruptedException { RMapCache map = redisson.getMapCache("simple01", new MsgPackJacksonCodec()); diff --git a/redisson/src/test/java/org/redisson/RedissonMapTest.java b/redisson/src/test/java/org/redisson/RedissonMapTest.java index 723bc2df6..0c681a53b 100644 --- a/redisson/src/test/java/org/redisson/RedissonMapTest.java +++ b/redisson/src/test/java/org/redisson/RedissonMapTest.java @@ -446,7 +446,43 @@ public class RedissonMapTest extends BaseMapTest { Assert.assertTrue(map.keySet().contains(new SimpleKey("33"))); Assert.assertFalse(map.keySet().contains(new SimpleKey("44"))); } + + @Test + public void testKeySetByPattern() { + RMap map = redisson.getMap("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200"); + map.put("30", "300"); + + assertThat(map.keySet("?0")).containsExactly("10", "20", "30"); + assertThat(map.keySet("1")).isEmpty(); + assertThat(map.keySet("10")).containsExactly("10"); + } + + @Test + public void testValuesByPattern() { + RMap map = redisson.getMap("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200"); + map.put("30", "300"); + assertThat(map.values("?0")).containsExactly("100", "200", "300"); + assertThat(map.values("1")).isEmpty(); + assertThat(map.values("10")).containsExactly("100"); + } + + @Test + public void testEntrySetByPattern() { + RMap map = redisson.getMap("simple", StringCodec.INSTANCE); + map.put("10", "100"); + map.put("20", "200"); + map.put("30", "300"); + + assertThat(map.entrySet("?0")).containsExactly(new AbstractMap.SimpleEntry("10", "100"), new AbstractMap.SimpleEntry("20", "200"), new AbstractMap.SimpleEntry("30", "300")); + assertThat(map.entrySet("1")).isEmpty(); + assertThat(map.entrySet("10")).containsExactly(new AbstractMap.SimpleEntry("10", "100")); + } + @Test public void testReadAllKeySet() { RMap map = redisson.getMap("simple");