diff --git a/redisson/src/main/java/org/redisson/RedissonKeys.java b/redisson/src/main/java/org/redisson/RedissonKeys.java index 9f1eb7c2f..53bbedf6e 100644 --- a/redisson/src/main/java/org/redisson/RedissonKeys.java +++ b/redisson/src/main/java/org/redisson/RedissonKeys.java @@ -48,6 +48,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -235,6 +236,29 @@ public class RedissonKeys implements RKeys { @Override public RFuture deleteByPatternAsync(String pattern) { + return eraseByPatternAsync(false, pattern); + } + + @Override + public long unlinkByPattern(String pattern) { + return commandExecutor.get(unlinkByPatternAsync(pattern)); + } + + @Override + public RFuture unlinkByPatternAsync(String pattern) { + return eraseByPatternAsync(true, pattern); + } + + private RFuture eraseByPatternAsync(boolean unlinkMode, String pattern) { + String commandName; + Function delegate; + if (unlinkMode) { + commandName = RedisCommands.UNLINK.getName(); + delegate = this::unlink; + } else { + commandName = RedisCommands.DEL.getName(); + delegate = this::delete; + } if (commandExecutor instanceof CommandBatchService || commandExecutor instanceof CommandReactiveBatchService || commandExecutor instanceof CommandRxBatchService) { @@ -246,9 +270,9 @@ public class RedissonKeys implements RKeys { "local keys = redis.call('keys', ARGV[1]) " + "local n = 0 " + "for i=1, #keys,5000 do " - + "n = n + redis.call('del', unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + + "n = n + redis.call(ARGV[2], unpack(keys, i, math.min(i+4999, table.getn(keys)))) " + "end " - + "return n;", Collections.emptyList(), pattern); + + "return n;", Collections.emptyList(), pattern, commandName); } int batchSize = 500; @@ -266,13 +290,13 @@ public class RedissonKeys implements RKeys { keys.add(key); if (keys.size() % batchSize == 0) { - count += delete(keys.toArray(new String[0])); + count += delegate.apply(keys.toArray(new String[0])); keys.clear(); } } if (!keys.isEmpty()) { - count += delete(keys.toArray(new String[0])); + count += delegate.apply(keys.toArray(new String[0])); keys.clear(); } diff --git a/redisson/src/main/java/org/redisson/api/RKeys.java b/redisson/src/main/java/org/redisson/api/RKeys.java index 55d0765e6..09197fee1 100644 --- a/redisson/src/main/java/org/redisson/api/RKeys.java +++ b/redisson/src/main/java/org/redisson/api/RKeys.java @@ -304,6 +304,21 @@ public interface RKeys extends RKeysAsync { */ long deleteByPattern(String pattern); + /** + * Unlink multiple objects by a key pattern. + *

+ * Method executes in NON atomic way in cluster mode due to lua script limitations. + *

+ * 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 - match pattern + * @return number of removed keys + */ + long unlinkByPattern(String pattern); + /** * Delete multiple objects * diff --git a/redisson/src/main/java/org/redisson/api/RKeysAsync.java b/redisson/src/main/java/org/redisson/api/RKeysAsync.java index e850ac87d..0355d7098 100644 --- a/redisson/src/main/java/org/redisson/api/RKeysAsync.java +++ b/redisson/src/main/java/org/redisson/api/RKeysAsync.java @@ -172,6 +172,21 @@ public interface RKeysAsync { */ RFuture deleteByPatternAsync(String pattern); + /** + * Unlink multiple objects by a key pattern. + *

+ * Method executes in NON atomic way in cluster mode due to lua script limitations. + *

+ * 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 - match pattern + * @return number of removed keys + */ + RFuture unlinkByPatternAsync(String pattern); + /** * Delete multiple objects * diff --git a/redisson/src/main/java/org/redisson/api/RKeysReactive.java b/redisson/src/main/java/org/redisson/api/RKeysReactive.java index c87738a87..74c609b42 100644 --- a/redisson/src/main/java/org/redisson/api/RKeysReactive.java +++ b/redisson/src/main/java/org/redisson/api/RKeysReactive.java @@ -230,6 +230,21 @@ public interface RKeysReactive { */ Mono deleteByPattern(String pattern); + /** + * Unlink multiple objects by a key pattern. + * + * Uses Lua script. + * + * 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 - match pattern + * @return deleted objects amount + */ + Mono unlinkByPattern(String pattern); + /** * Delete multiple objects by name. * diff --git a/redisson/src/main/java/org/redisson/api/RKeysRx.java b/redisson/src/main/java/org/redisson/api/RKeysRx.java index 1fc1b0a5e..4b55124ec 100644 --- a/redisson/src/main/java/org/redisson/api/RKeysRx.java +++ b/redisson/src/main/java/org/redisson/api/RKeysRx.java @@ -232,6 +232,21 @@ public interface RKeysRx { */ Single deleteByPattern(String pattern); + /** + * Unlink multiple objects by a key pattern. + * + * Uses Lua script. + * + * 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 - match pattern + * @return deleted objects amount + */ + Single unlinkByPattern(String pattern); + /** * Delete multiple objects by name. * diff --git a/redisson/src/test/java/org/redisson/RedissonKeysReactiveTest.java b/redisson/src/test/java/org/redisson/RedissonKeysReactiveTest.java index 243549af8..380c12207 100644 --- a/redisson/src/test/java/org/redisson/RedissonKeysReactiveTest.java +++ b/redisson/src/test/java/org/redisson/RedissonKeysReactiveTest.java @@ -135,6 +135,16 @@ public class RedissonKeysReactiveTest extends BaseReactiveTest { Assertions.assertEquals(2, sync(redisson.getKeys().deleteByPattern("test?")).intValue()); } + @Test + public void testUnlinkByPattern() { + RBucketReactive bucket = redisson.getBucket("test1"); + sync(bucket.set("someValue")); + RMapReactive map = redisson.getMap("test2"); + sync(map.fastPut("1", "2")); + + Assertions.assertEquals(2, sync(redisson.getKeys().unlinkByPattern("test?")).intValue()); + } + @Test public void testMassDelete() { RBucketReactive bucket = redisson.getBucket("test"); diff --git a/redisson/src/test/java/org/redisson/RedissonKeysTest.java b/redisson/src/test/java/org/redisson/RedissonKeysTest.java index 9abed9f7d..26c30dba8 100644 --- a/redisson/src/test/java/org/redisson/RedissonKeysTest.java +++ b/redisson/src/test/java/org/redisson/RedissonKeysTest.java @@ -3,6 +3,8 @@ package org.redisson; import org.awaitility.Awaitility; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.redisson.api.*; import org.redisson.api.listener.FlushListener; import org.redisson.api.listener.NewObjectListener; @@ -292,8 +294,9 @@ public class RedissonKeysTest extends RedisDockerTest { }); } - @Test - public void testDeleteByPattern() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testDeleteByPattern(boolean unlinkMode) { RBucket bucket = redisson.getBucket("test0"); bucket.set("someValue3"); assertThat(bucket.isExists()).isTrue(); @@ -310,13 +313,20 @@ public class RedissonKeysTest extends RedisDockerTest { map2.fastPut("1", "5"); assertThat(map2.isExists()).isTrue(); - assertThat(redisson.getKeys().deleteByPattern("test?")).isEqualTo(4); - assertThat(redisson.getKeys().deleteByPattern("test?")).isZero(); + if(unlinkMode) { + assertThat(redisson.getKeys().unlinkByPattern("test?")).isEqualTo(4); + assertThat(redisson.getKeys().unlinkByPattern("test?")).isZero(); + } else { + assertThat(redisson.getKeys().deleteByPattern("test?")).isEqualTo(4); + assertThat(redisson.getKeys().deleteByPattern("test?")).isZero(); + } + assertThat(redisson.getKeys().count()).isZero(); } - @Test - public void testDeleteByPatternBatch() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testDeleteByPatternBatch(boolean unlinkMode) { RBucket bucket = redisson.getBucket("test0"); bucket.set("someValue3"); assertThat(bucket.isExists()).isTrue(); @@ -335,7 +345,11 @@ public class RedissonKeysTest extends RedisDockerTest { RBatch batch = redisson.createBatch(); - batch.getKeys().deleteByPatternAsync("test?"); + if(unlinkMode) { + batch.getKeys().unlinkByPatternAsync("test?"); + } else { + batch.getKeys().deleteByPatternAsync("test?"); + } BatchResult r = batch.execute(); Assertions.assertEquals(4L, r.getResponses().get(0)); } diff --git a/redisson/src/test/java/org/redisson/rx/RedissonKeysRxTest.java b/redisson/src/test/java/org/redisson/rx/RedissonKeysRxTest.java index 2c93d98db..897e4c072 100644 --- a/redisson/src/test/java/org/redisson/rx/RedissonKeysRxTest.java +++ b/redisson/src/test/java/org/redisson/rx/RedissonKeysRxTest.java @@ -62,6 +62,16 @@ public class RedissonKeysRxTest extends BaseRxTest { Assertions.assertEquals(2, sync(redisson.getKeys().deleteByPattern("test?")).intValue()); } + @Test + public void testUnlinkByPattern() { + RBucketRx bucket = redisson.getBucket("test1"); + sync(bucket.set("someValue")); + RMapRx map = redisson.getMap("test2"); + sync(map.fastPut("1", "2")); + + Assertions.assertEquals(2, sync(redisson.getKeys().unlinkByPattern("test?")).intValue()); + } + @Test public void testMassDelete() { RBucketRx bucket = redisson.getBucket("test");