From 2bb56965b9943ced8f1c953ecdb1bd3a93b99aa1 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Wed, 5 Nov 2025 17:50:47 -0600 Subject: [PATCH 1/4] 3250 - Prepare branch --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2bfef0f8f3..cc064abce2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 4.1.0-SNAPSHOT + 4.1.0-3250-SNAPSHOT Spring Data Redis Spring Data module for Redis From 1dda1676693958f128df724b9046a476cb5444c5 Mon Sep 17 00:00:00 2001 From: "viktoriya.kutsarova" Date: Tue, 28 Oct 2025 14:28:47 +0200 Subject: [PATCH 2/4] Expand bitwise operations with DIFF, DIFF1, ANDOR, ONE. Original pull request #3250 Closes #3250 Signed-off-by: viktoriya.kutsarova --- .../connection/ReactiveStringCommands.java | 2 +- .../redis/connection/RedisStringCommands.java | 2 +- .../connection/jedis/JedisConverters.java | 4 + .../LettuceReactiveStringCommands.java | 6 +- .../lettuce/LettuceStringCommands.java | 5 + .../AbstractConnectionIntegrationTests.java | 36 +++++++ ...ultStringRedisConnectionPipelineTests.java | 84 ++++++++++++++++ ...tStringRedisConnectionPipelineTxTests.java | 84 ++++++++++++++++ .../DefaultStringRedisConnectionTests.java | 98 +++++++++++++++++++ .../DefaultStringRedisConnectionTxTests.java | 84 ++++++++++++++++ .../jedis/JedisClusterConnectionTests.java | 77 +++++++++++++++ .../LettuceClusterConnectionTests.java | 77 +++++++++++++++ ...ClusterStringCommandsIntegrationTests.java | 65 ++++++++++++ ...eactiveStringCommandsIntegrationTests.java | 65 ++++++++++++ 14 files changed, 686 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java index ae941374f0..033fac7ce4 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java @@ -1289,7 +1289,7 @@ default Mono bitPos(ByteBuffer key, boolean bit, Range range) { } /** - * Emmit the the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at + * Emmit the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at * {@literal key}. * * @param commands must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java index 9f3c7c15db..e3dd392c87 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java @@ -38,7 +38,7 @@ public interface RedisStringCommands { enum BitOperation { - AND, OR, XOR, NOT; + AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR, ONE; } /** diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index 98ba6a5525..b6f703ccc3 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -285,6 +285,10 @@ public static BitOP toBitOp(BitOperation bitOp) { case OR -> BitOP.OR; case NOT -> BitOP.NOT; case XOR -> BitOP.XOR; + case DIFF -> BitOP.DIFF; + case DIFF1 -> BitOP.DIFF1; + case ANDOR -> BitOP.ANDOR; + case ONE -> BitOP.ONE; }; } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java index 20cee30e23..6ca36d10fa 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java @@ -22,6 +22,7 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -368,7 +369,10 @@ public Flux> bitOp(Publisher c Assert.isTrue(sourceKeys.length == 1, "BITOP NOT does not allow more than 1 source key."); yield reactiveCommands.bitopNot(destinationKey, sourceKeys[0]); } - default -> throw new IllegalArgumentException("Unknown BITOP '%s'".formatted(command.getBitOp())); + case DIFF -> reactiveCommands.bitopDiff(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); + case DIFF1 -> reactiveCommands.bitopDiff1(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); + case ANDOR -> reactiveCommands.bitopAndor(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); + case ONE -> reactiveCommands.bitopOne(destinationKey, sourceKeys); }; return result.map(value -> new NumericResponse<>(command, value)); diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java index 87ab4b3e83..ffb3513002 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java @@ -18,6 +18,7 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.api.async.RedisStringAsyncCommands; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -307,6 +308,10 @@ public Long bitOp(@NonNull BitOperation op, byte @NonNull [] destination, byte @ } yield it.bitopNot(destination, keys[0]); } + case DIFF -> it.bitopDiff(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); + case DIFF1 -> it.bitopDiff1(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); + case ANDOR -> it.bitopAndor(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); + case ONE -> it.bitopOne(destination, keys); }); } diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index d09bb78ddf..209e89e179 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -571,6 +571,42 @@ void testBitOpNotMultipleSources() { .isThrownBy(() -> connection.bitOp(BitOperation.NOT, "key3", "key1", "key2")); } + @Test + void testBitOpDiff() { + + actual.add(connection.set("key1", "foobar")); + actual.add(connection.set("key2", "abcdef")); + actual.add(connection.bitOp(BitOperation.DIFF, "key3", "key1", "key2")); + verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 6L)); + } + + @Test + void testBitOpDiff1() { + + actual.add(connection.set("key1", "foobar")); + actual.add(connection.set("key2", "abcdef")); + actual.add(connection.bitOp(BitOperation.DIFF1, "key3", "key1", "key2")); + verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 6L)); + } + + @Test + void testBitOpAndor() { + + actual.add(connection.set("key1", "foo")); + actual.add(connection.set("key2", "bar")); + actual.add(connection.bitOp(BitOperation.ANDOR, "key3", "key1", "key2")); + verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 3L)); + } + + @Test + void testBitOpOne() { + + actual.add(connection.set("key1", "foo")); + actual.add(connection.set("key2", "bar")); + actual.add(connection.bitOp(BitOperation.ONE, "key3", "key1", "key2")); + verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 3L)); + } + @Test @EnabledOnCommand("COPY") void testCopy() { diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java index 0d977e00fa..1b290c2f0a 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -1010,6 +1010,90 @@ public void testBitOp() { super.testBitOp(); } + @Test + public void testBitOpOrBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpOrBytes(); + } + + @Test + public void testBitOpOr() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpOr(); + } + + @Test + public void testBitOpXorBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpXorBytes(); + } + + @Test + public void testBitOpXor() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpXor(); + } + + @Test + public void testBitOpNotBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpNotBytes(); + } + + @Test + public void testBitOpNot() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpNot(); + } + + @Test + public void testBitOpDiffBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpDiffBytes(); + } + + @Test + public void testBitOpDiff() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpDiff(); + } + + @Test + public void testBitOpDiff1Bytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpDiff1Bytes(); + } + + @Test + public void testBitOpDiff1() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpDiff1(); + } + + @Test + public void testBitOpAndorBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpAndorBytes(); + } + + @Test + public void testBitOpAndor() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpAndor(); + } + + @Test + public void testBitOpOneBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpOneBytes(); + } + + @Test + public void testBitOpOne() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); + super.testBitOpOne(); + } + @Test public void testSUnionBytes() { doReturn(Collections.singletonList(bytesSet)).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index 6ad92f531b..88da63cd0a 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -1025,6 +1025,90 @@ public void testBitOp() { super.testBitOp(); } + @Test + public void testBitOpOrBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpOrBytes(); + } + + @Test + public void testBitOpOr() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpOr(); + } + + @Test + public void testBitOpXorBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpXorBytes(); + } + + @Test + public void testBitOpXor() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpXor(); + } + + @Test + public void testBitOpNotBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpNotBytes(); + } + + @Test + public void testBitOpNot() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpNot(); + } + + @Test + public void testBitOpDiffBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpDiffBytes(); + } + + @Test + public void testBitOpDiff() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpDiff(); + } + + @Test + public void testBitOpDiff1Bytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpDiff1Bytes(); + } + + @Test + public void testBitOpDiff1() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpDiff1(); + } + + @Test + public void testBitOpAndorBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpAndorBytes(); + } + + @Test + public void testBitOpAndor() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpAndor(); + } + + @Test + public void testBitOpOneBytes() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpOneBytes(); + } + + @Test + public void testBitOpOne() { + doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); + super.testBitOpOne(); + } + @Test public void testSUnionBytes() { doReturn(Collections.singletonList(Collections.singletonList(bytesSet))).when(nativeConnection).closePipeline(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index e57589012a..8eab7d5061 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -1266,6 +1266,104 @@ public void testBitOp() { verifyResults(Collections.singletonList(5L)); } + @Test + public void testBitOpOrBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.OR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.OR, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpOr() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.OR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.OR, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpXorBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.XOR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.XOR, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpXor() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.XOR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.XOR, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpNotBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.NOT, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.NOT, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpNot() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.NOT, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.NOT, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpDiffBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.DIFF, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpDiff() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.DIFF, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpDiff1Bytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF1, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.DIFF1, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpDiff1() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF1, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.DIFF1, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpAndorBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.ANDOR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.ANDOR, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpAndor() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.ANDOR, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.ANDOR, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpOneBytes() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.ONE, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.ONE, fooBytes, barBytes)); + verifyResults(Collections.singletonList(5L)); + } + + @Test + public void testBitOpOne() { + doReturn(5L).when(nativeConnection).bitOp(BitOperation.ONE, fooBytes, barBytes); + actual.add(connection.bitOp(BitOperation.ONE, foo, bar)); + verifyResults(Collections.singletonList(5L)); + } + @Test public void testSUnionBytes() { doReturn(bytesSet).when(nativeConnection).sUnion(fooBytes, barBytes); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java index 0e8fa6c1fb..3c9cdf0d62 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java @@ -995,6 +995,90 @@ public void testBitOp() { super.testBitOp(); } + @Test + public void testBitOpOrBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpOrBytes(); + } + + @Test + public void testBitOpOr() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpOr(); + } + + @Test + public void testBitOpXorBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpXorBytes(); + } + + @Test + public void testBitOpXor() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpXor(); + } + + @Test + public void testBitOpNotBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpNotBytes(); + } + + @Test + public void testBitOpNot() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpNot(); + } + + @Test + public void testBitOpDiffBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpDiffBytes(); + } + + @Test + public void testBitOpDiff() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpDiff(); + } + + @Test + public void testBitOpDiff1Bytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpDiff1Bytes(); + } + + @Test + public void testBitOpDiff1() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpDiff1(); + } + + @Test + public void testBitOpAndorBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpAndorBytes(); + } + + @Test + public void testBitOpAndor() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpAndor(); + } + + @Test + public void testBitOpOneBytes() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpOneBytes(); + } + + @Test + public void testBitOpOne() { + doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); + super.testBitOpOne(); + } + @Test public void testSUnionBytes() { doReturn(Collections.singletonList(bytesSet)).when(nativeConnection).exec(); diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 08ee13c910..497fabeccb 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -78,6 +78,7 @@ * @author Pavel Khokhlov * @author Dennis Neufeld * @author Tihomir Mateev + * @author Viktoriya Kutsarova */ @EnabledOnRedisClusterAvailable @ExtendWith(JedisExtension.class) @@ -191,6 +192,82 @@ void bitOpShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("bab"); } + @Test + void bitOpOrShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "ugh"); + + clusterConnection.bitOp(BitOperation.OR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("woo"); + } + + @Test + void bitOpXorShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "aaa"); + nativeConnection.set(SAME_SLOT_KEY_2, "___"); + + clusterConnection.bitOp(BitOperation.XOR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo(">>>"); + } + + @Test + void bitOpNotShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + + clusterConnection.bitOp(BitOperation.NOT, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpDiffShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); + nativeConnection.set(SAME_SLOT_KEY_2, "abcdef"); + + clusterConnection.bitOp(BitOperation.DIFF, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpDiff1ShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); + nativeConnection.set(SAME_SLOT_KEY_2, "abcdef"); + + clusterConnection.bitOp(BitOperation.DIFF1, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpAndorShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "bar"); + + clusterConnection.bitOp(BitOperation.ANDOR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpOneShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "bar"); + + clusterConnection.bitOp(BitOperation.ONE, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + @Test // DATAREDIS-315 public void blPopShouldPopElementCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index af6285dad9..562d50d93b 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -76,6 +76,7 @@ * @author Mark Paluch * @author Dennis Neufeld * @author Tihomir Mateev + * @author Viktoriya Kutsarova */ @SuppressWarnings("deprecation") @EnabledOnRedisClusterAvailable @@ -279,6 +280,82 @@ void bitOpShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("bab"); } + @Test + void bitOpOrShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "ugh"); + + clusterConnection.bitOp(BitOperation.OR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("woo"); + } + + @Test + void bitOpXorShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "aaa"); + nativeConnection.set(SAME_SLOT_KEY_2, "___"); + + clusterConnection.bitOp(BitOperation.XOR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo(">>>"); + } + + @Test + void bitOpNotShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + + clusterConnection.bitOp(BitOperation.NOT, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpDiffShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); + nativeConnection.set(SAME_SLOT_KEY_2, "abcdef"); + + clusterConnection.bitOp(BitOperation.DIFF, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpDiff1ShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); + nativeConnection.set(SAME_SLOT_KEY_2, "abcdef"); + + clusterConnection.bitOp(BitOperation.DIFF1, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpAndorShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "bar"); + + clusterConnection.bitOp(BitOperation.ANDOR, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpOneShouldWorkCorrectly() { + + nativeConnection.set(SAME_SLOT_KEY_1, "foo"); + nativeConnection.set(SAME_SLOT_KEY_2, "bar"); + + clusterConnection.bitOp(BitOperation.ONE, SAME_SLOT_KEY_3_BYTES, SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES); + + assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); + } + @Test // DATAREDIS-315 public void blPopShouldPopElementCorrectly() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java index 1f56137cf2..3b3143e716 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java @@ -83,6 +83,27 @@ void bitOpOrShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isEqualTo(VALUE_3); } + @Test + void bitOpXorShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); + nativeCommands.set(SAME_SLOT_KEY_2, VALUE_2); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER, SAME_SLOT_KEY_2_BBUFFER), + RedisStringCommands.BitOperation.XOR, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(7L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpNotShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER), + RedisStringCommands.BitOperation.NOT, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(7L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + @Test // DATAREDIS-525 void bitNotShouldThrowExceptionWhenMoreThanOnSourceKeyAndKeysMapToSameSlot() { assertThatIllegalArgumentException().isThrownBy( @@ -90,4 +111,48 @@ void bitNotShouldThrowExceptionWhenMoreThanOnSourceKeyAndKeysMapToSameSlot() { RedisStringCommands.BitOperation.NOT, SAME_SLOT_KEY_3_BBUFFER).block()); } + @Test + void bitOpDiffShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, "foobar"); + nativeCommands.set(SAME_SLOT_KEY_2, "abcdef"); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER, SAME_SLOT_KEY_2_BBUFFER), + RedisStringCommands.BitOperation.DIFF, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(6L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpDiff1ShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, "foobar"); + nativeCommands.set(SAME_SLOT_KEY_2, "abcdef"); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER, SAME_SLOT_KEY_2_BBUFFER), + RedisStringCommands.BitOperation.DIFF1, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(6L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpAndorShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); + nativeCommands.set(SAME_SLOT_KEY_2, VALUE_2); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER, SAME_SLOT_KEY_2_BBUFFER), + RedisStringCommands.BitOperation.ANDOR, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(7L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + + @Test + void bitOpOneShouldWorkAsExpectedWhenKeysMapToSameSlot() { + + nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); + nativeCommands.set(SAME_SLOT_KEY_2, VALUE_2); + + assertThat(connection.stringCommands().bitOp(Arrays.asList(SAME_SLOT_KEY_1_BBUFFER, SAME_SLOT_KEY_2_BBUFFER), + RedisStringCommands.BitOperation.ONE, SAME_SLOT_KEY_3_BBUFFER).block()).isEqualTo(7L); + assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java index 5f233cd40c..5634d283a6 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java @@ -61,6 +61,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Michele Mancioppi + * @author Viktoriya Kutsarova */ @ParameterizedClass public class LettuceReactiveStringCommandsIntegrationTests extends LettuceReactiveCommandsTestSupport { @@ -508,6 +509,70 @@ void bitNotShouldThrowExceptionWhenMoreThanOnSourceKey() { .verify(); } + @Test + void bitOpDiffShouldWorkAsExpected() { + + assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); + + nativeCommands.set(KEY_1, "foobar"); + nativeCommands.set(KEY_2, "abcdef"); + + connection.stringCommands().bitOp(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), BitOperation.DIFF, KEY_3_BBUFFER) + .as(StepVerifier::create) // + .expectNext(6L) // + .verifyComplete(); + + assertThat(nativeCommands.get(KEY_3)).isNotNull(); + } + + @Test + void bitOpDiff1ShouldWorkAsExpected() { + + assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); + + nativeCommands.set(KEY_1, "foobar"); + nativeCommands.set(KEY_2, "abcdef"); + + connection.stringCommands().bitOp(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), BitOperation.DIFF1, KEY_3_BBUFFER) + .as(StepVerifier::create) // + .expectNext(6L) // + .verifyComplete(); + + assertThat(nativeCommands.get(KEY_3)).isNotNull(); + } + + @Test + void bitOpAndorShouldWorkAsExpected() { + + assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); + + nativeCommands.set(KEY_1, "foo"); + nativeCommands.set(KEY_2, "bar"); + + connection.stringCommands().bitOp(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), BitOperation.ANDOR, KEY_3_BBUFFER) + .as(StepVerifier::create) // + .expectNext(3L) // + .verifyComplete(); + + assertThat(nativeCommands.get(KEY_3)).isNotNull(); + } + + @Test + void bitOpOneShouldWorkAsExpected() { + + assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); + + nativeCommands.set(KEY_1, VALUE_1); + nativeCommands.set(KEY_2, VALUE_2); + + connection.stringCommands().bitOp(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), BitOperation.ONE, KEY_3_BBUFFER) + .as(StepVerifier::create) // + .expectNext(7L) // + .verifyComplete(); + + assertThat(nativeCommands.get(KEY_3)).isNotNull(); + } + @Test // DATAREDIS-525 void strLenShouldReturnValueCorrectly() { From ccb19cb0bed43c45c9917ebd9b4f220c6ebd45c9 Mon Sep 17 00:00:00 2001 From: Chris Bono Date: Wed, 5 Nov 2025 17:47:16 -0600 Subject: [PATCH 3/4] Polishing. Add author tags and issue number comment for new tests. See #3250 Signed-off-by: Chris Bono --- .../connection/ReactiveStringCommands.java | 2 +- .../redis/connection/RedisStringCommands.java | 1 + .../connection/jedis/JedisConverters.java | 1 + .../LettuceReactiveStringCommands.java | 1 + .../lettuce/LettuceStringCommands.java | 1 + .../AbstractConnectionIntegrationTests.java | 9 +++--- ...ultStringRedisConnectionPipelineTests.java | 29 ++++++++++--------- ...tStringRedisConnectionPipelineTxTests.java | 29 ++++++++++--------- .../DefaultStringRedisConnectionTests.java | 17 ++++++----- .../DefaultStringRedisConnectionTxTests.java | 29 ++++++++++--------- .../jedis/JedisClusterConnectionTests.java | 14 ++++----- .../LettuceClusterConnectionTests.java | 14 ++++----- ...ClusterStringCommandsIntegrationTests.java | 13 +++++---- ...eactiveStringCommandsIntegrationTests.java | 8 ++--- 14 files changed, 89 insertions(+), 79 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java index 033fac7ce4..af8f785490 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveStringCommands.java @@ -1289,7 +1289,7 @@ default Mono bitPos(ByteBuffer key, boolean bit, Range range) { } /** - * Emmit the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at + * Emit the position of the first bit set to given {@code bit} in a string. Get the length of the value stored at * {@literal key}. * * @param commands must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java index e3dd392c87..d70df0ad36 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java @@ -32,6 +32,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Marcin Grzejszczak + * @author Viktoriya Kutsarova * @see RedisCommands */ @NullUnmarked diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index b6f703ccc3..ed87de789f 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -109,6 +109,7 @@ * @author Guy Korland * @author dengliming * @author John Blum + * @author Viktoriya Kutsarova */ @SuppressWarnings("ConstantConditions") abstract class JedisConverters extends Converters { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java index 6ca36d10fa..440814d390 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java @@ -50,6 +50,7 @@ * @author Michele Mancioppi * @author John Blum * @author Marcin Grzejszczak + * @author Viktoriya Kutsarova * @since 2.0 */ class LettuceReactiveStringCommands implements ReactiveStringCommands { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java index ffb3513002..a67d0ea4c5 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java @@ -40,6 +40,7 @@ * @author dengliming * @author John Blum * @author Marcin Grzejszczak + * @author Viktoriya Kutsarova * @since 2.0 */ @NullUnmarked diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 209e89e179..5493467b40 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -117,6 +117,7 @@ * @author Roman Osadchuk * @author Tihomir Mateev * @author Jeonggyu Choi + * @author Viktoriya Kutsarova */ public abstract class AbstractConnectionIntegrationTests { @@ -571,7 +572,7 @@ void testBitOpNotMultipleSources() { .isThrownBy(() -> connection.bitOp(BitOperation.NOT, "key3", "key1", "key2")); } - @Test + @Test // GH-3250 void testBitOpDiff() { actual.add(connection.set("key1", "foobar")); @@ -580,7 +581,7 @@ void testBitOpDiff() { verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 6L)); } - @Test + @Test // GH-3250 void testBitOpDiff1() { actual.add(connection.set("key1", "foobar")); @@ -589,7 +590,7 @@ void testBitOpDiff1() { verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 6L)); } - @Test + @Test // GH-3250 void testBitOpAndor() { actual.add(connection.set("key1", "foo")); @@ -598,7 +599,7 @@ void testBitOpAndor() { verifyResults(Arrays.asList(Boolean.TRUE, Boolean.TRUE, 3L)); } - @Test + @Test // GH-3250 void testBitOpOne() { actual.add(connection.set("key1", "foo")); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java index 1b290c2f0a..bf3e962eb7 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTests.java @@ -38,6 +38,7 @@ * @author Ninad Divadkar * @author Mark Paluch * @author dengliming + * @author Viktoriya Kutsarova */ public class DefaultStringRedisConnectionPipelineTests extends DefaultStringRedisConnectionTests { @@ -1010,85 +1011,85 @@ public void testBitOp() { super.testBitOp(); } - @Test + @Test // GH-3250 public void testBitOpOrBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpOrBytes(); } - @Test + @Test // GH-3250 public void testBitOpOr() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpOr(); } - @Test + @Test // GH-3250 public void testBitOpXorBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpXorBytes(); } - @Test + @Test // GH-3250 public void testBitOpXor() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpXor(); } - @Test + @Test // GH-3250 public void testBitOpNotBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpNotBytes(); } - @Test + @Test // GH-3250 public void testBitOpNot() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpNot(); } - @Test + @Test // GH-3250 public void testBitOpDiffBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpDiffBytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpDiff(); } - @Test + @Test // GH-3250 public void testBitOpDiff1Bytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpDiff1Bytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff1() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpDiff1(); } - @Test + @Test // GH-3250 public void testBitOpAndorBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpAndorBytes(); } - @Test + @Test // GH-3250 public void testBitOpAndor() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpAndor(); } - @Test + @Test // GH-3250 public void testBitOpOneBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpOneBytes(); } - @Test + @Test // GH-3250 public void testBitOpOne() { doReturn(Collections.singletonList(5L)).when(nativeConnection).closePipeline(); super.testBitOpOne(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java index 88da63cd0a..a0fb3f2faf 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionPipelineTxTests.java @@ -37,6 +37,7 @@ * @author Ninad Divadkar * @author Mark Paluch * @author dengliming + * @author Viktoriya Kutsarova */ public class DefaultStringRedisConnectionPipelineTxTests extends DefaultStringRedisConnectionTxTests { @@ -1025,85 +1026,85 @@ public void testBitOp() { super.testBitOp(); } - @Test + @Test // GH-3250 public void testBitOpOrBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpOrBytes(); } - @Test + @Test // GH-3250 public void testBitOpOr() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpOr(); } - @Test + @Test // GH-3250 public void testBitOpXorBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpXorBytes(); } - @Test + @Test // GH-3250 public void testBitOpXor() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpXor(); } - @Test + @Test // GH-3250 public void testBitOpNotBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpNotBytes(); } - @Test + @Test // GH-3250 public void testBitOpNot() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpNot(); } - @Test + @Test // GH-3250 public void testBitOpDiffBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpDiffBytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpDiff(); } - @Test + @Test // GH-3250 public void testBitOpDiff1Bytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpDiff1Bytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff1() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpDiff1(); } - @Test + @Test // GH-3250 public void testBitOpAndorBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpAndorBytes(); } - @Test + @Test // GH-3250 public void testBitOpAndor() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpAndor(); } - @Test + @Test // GH-3250 public void testBitOpOneBytes() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpOneBytes(); } - @Test + @Test // GH-3250 public void testBitOpOne() { doReturn(Collections.singletonList(Collections.singletonList(5L))).when(nativeConnection).closePipeline(); super.testBitOpOne(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index 8eab7d5061..20947d6c63 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -78,6 +78,7 @@ * @author Mark Paluch * @author dengliming * @author ihaohong + * @author Viktoriya Kutsarova */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -1308,56 +1309,56 @@ public void testBitOpNot() { verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpDiffBytes() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.DIFF, fooBytes, barBytes)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpDiff() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.DIFF, foo, bar)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpDiff1Bytes() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF1, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.DIFF1, fooBytes, barBytes)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpDiff1() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.DIFF1, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.DIFF1, foo, bar)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpAndorBytes() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.ANDOR, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.ANDOR, fooBytes, barBytes)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpAndor() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.ANDOR, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.ANDOR, foo, bar)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpOneBytes() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.ONE, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.ONE, fooBytes, barBytes)); verifyResults(Collections.singletonList(5L)); } - @Test + @Test // GH-3250 public void testBitOpOne() { doReturn(5L).when(nativeConnection).bitOp(BitOperation.ONE, fooBytes, barBytes); actual.add(connection.bitOp(BitOperation.ONE, foo, bar)); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java index 3c9cdf0d62..051d391478 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTxTests.java @@ -34,6 +34,7 @@ * @author Jennifer Hickey * @author Christoph Strobl * @author Ninad Divadkar + * @author Viktoriya Kutsarova */ public class DefaultStringRedisConnectionTxTests extends DefaultStringRedisConnectionTests { @@ -995,85 +996,85 @@ public void testBitOp() { super.testBitOp(); } - @Test + @Test // GH-3250 public void testBitOpOrBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpOrBytes(); } - @Test + @Test // GH-3250 public void testBitOpOr() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpOr(); } - @Test + @Test // GH-3250 public void testBitOpXorBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpXorBytes(); } - @Test + @Test // GH-3250 public void testBitOpXor() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpXor(); } - @Test + @Test // GH-3250 public void testBitOpNotBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpNotBytes(); } - @Test + @Test // GH-3250 public void testBitOpNot() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpNot(); } - @Test + @Test // GH-3250 public void testBitOpDiffBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpDiffBytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpDiff(); } - @Test + @Test // GH-3250 public void testBitOpDiff1Bytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpDiff1Bytes(); } - @Test + @Test // GH-3250 public void testBitOpDiff1() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpDiff1(); } - @Test + @Test // GH-3250 public void testBitOpAndorBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpAndorBytes(); } - @Test + @Test // GH-3250 public void testBitOpAndor() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpAndor(); } - @Test + @Test // GH-3250 public void testBitOpOneBytes() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpOneBytes(); } - @Test + @Test // GH-3250 public void testBitOpOne() { doReturn(Collections.singletonList(5L)).when(nativeConnection).exec(); super.testBitOpOne(); diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 497fabeccb..72beaa1321 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -192,7 +192,7 @@ void bitOpShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("bab"); } - @Test + @Test // GH-3250 void bitOpOrShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -203,7 +203,7 @@ void bitOpOrShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("woo"); } - @Test + @Test // GH-3250 void bitOpXorShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "aaa"); @@ -214,7 +214,7 @@ void bitOpXorShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo(">>>"); } - @Test + @Test // GH-3250 void bitOpNotShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -224,7 +224,7 @@ void bitOpNotShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiffShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); @@ -235,7 +235,7 @@ void bitOpDiffShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiff1ShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); @@ -246,7 +246,7 @@ void bitOpDiff1ShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpAndorShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -257,7 +257,7 @@ void bitOpAndorShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpOneShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index 562d50d93b..80d7e4db23 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -280,7 +280,7 @@ void bitOpShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("bab"); } - @Test + @Test // GH-3250 void bitOpOrShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -291,7 +291,7 @@ void bitOpOrShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo("woo"); } - @Test + @Test // GH-3250 void bitOpXorShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "aaa"); @@ -302,7 +302,7 @@ void bitOpXorShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isEqualTo(">>>"); } - @Test + @Test // GH-3250 void bitOpNotShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -312,7 +312,7 @@ void bitOpNotShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiffShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); @@ -323,7 +323,7 @@ void bitOpDiffShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiff1ShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foobar"); @@ -334,7 +334,7 @@ void bitOpDiff1ShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpAndorShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); @@ -345,7 +345,7 @@ void bitOpAndorShouldWorkCorrectly() { assertThat(nativeConnection.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpOneShouldWorkCorrectly() { nativeConnection.set(SAME_SLOT_KEY_1, "foo"); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java index 3b3143e716..8078a58513 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterStringCommandsIntegrationTests.java @@ -29,6 +29,7 @@ /** * @author Christoph Strobl + * @author Viktoriya Kutsarova * @since 2.0 */ class LettuceReactiveClusterStringCommandsIntegrationTests extends LettuceReactiveClusterTestSupport { @@ -83,7 +84,7 @@ void bitOpOrShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isEqualTo(VALUE_3); } - @Test + @Test // GH-3250 void bitOpXorShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); @@ -94,7 +95,7 @@ void bitOpXorShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpNotShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); @@ -111,7 +112,7 @@ void bitNotShouldThrowExceptionWhenMoreThanOnSourceKeyAndKeysMapToSameSlot() { RedisStringCommands.BitOperation.NOT, SAME_SLOT_KEY_3_BBUFFER).block()); } - @Test + @Test // GH-3250 void bitOpDiffShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, "foobar"); @@ -122,7 +123,7 @@ void bitOpDiffShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiff1ShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, "foobar"); @@ -133,7 +134,7 @@ void bitOpDiff1ShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpAndorShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); @@ -144,7 +145,7 @@ void bitOpAndorShouldWorkAsExpectedWhenKeysMapToSameSlot() { assertThat(nativeCommands.get(SAME_SLOT_KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpOneShouldWorkAsExpectedWhenKeysMapToSameSlot() { nativeCommands.set(SAME_SLOT_KEY_1, VALUE_1); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java index 5634d283a6..b0ab1bb84f 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommandsIntegrationTests.java @@ -509,7 +509,7 @@ void bitNotShouldThrowExceptionWhenMoreThanOnSourceKey() { .verify(); } - @Test + @Test // GH-3250 void bitOpDiffShouldWorkAsExpected() { assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); @@ -525,7 +525,7 @@ void bitOpDiffShouldWorkAsExpected() { assertThat(nativeCommands.get(KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpDiff1ShouldWorkAsExpected() { assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); @@ -541,7 +541,7 @@ void bitOpDiff1ShouldWorkAsExpected() { assertThat(nativeCommands.get(KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpAndorShouldWorkAsExpected() { assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); @@ -557,7 +557,7 @@ void bitOpAndorShouldWorkAsExpected() { assertThat(nativeCommands.get(KEY_3)).isNotNull(); } - @Test + @Test // GH-3250 void bitOpOneShouldWorkAsExpected() { assumeTrue(connectionProvider instanceof StandaloneConnectionProvider); From cca675ea1c81ad3d710391c9645bd38e398060c3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 7 Nov 2025 10:49:19 +0100 Subject: [PATCH 4/4] Polishing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add since tags, extract KeyUtil to replace duplicate calls to Arrays.copyOfRange(…) to split a concatenated list of elements into the first and remaining elements. See #3250 --- .../redis/connection/RedisStringCommands.java | 23 +++++- .../jedis/JedisClusterSetCommands.java | 31 +++---- .../lettuce/LettuceClusterSetCommands.java | 29 +++---- .../LettuceReactiveStringCommands.java | 11 ++- .../lettuce/LettuceStringCommands.java | 9 ++- .../data/redis/util/KeyUtils.java | 81 +++++++++++++++++++ 6 files changed, 146 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/springframework/data/redis/util/KeyUtils.java diff --git a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java index d70df0ad36..a8b55174a7 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisStringCommands.java @@ -39,7 +39,28 @@ public interface RedisStringCommands { enum BitOperation { - AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR, ONE; + + AND, OR, XOR, NOT, + + /** + * @since 4.1 + */ + DIFF, + + /** + * @since 4.1 + */ + DIFF1, + + /** + * @since 4.1 + */ + ANDOR, + + /** + * @since 4.1 + */ + ONE; } /** diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java index de3c66e270..57c105fce9 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterSetCommands.java @@ -35,6 +35,7 @@ import org.springframework.data.redis.core.ScanIteration; import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.util.ByteUtils; +import org.springframework.data.redis.util.KeyUtils; import org.springframework.util.Assert; /** @@ -320,25 +321,25 @@ public Set sDiff(byte[]... keys) { } } - byte[] source = keys[0]; - byte[][] others = Arrays.copyOfRange(keys, 1, keys.length); + return KeyUtils.splitKeys(keys, (source, others) -> { - ByteArraySet values = new ByteArraySet(sMembers(source)); - Collection> resultList = connection.getClusterCommandExecutor() - .executeMultiKeyCommand( - (JedisMultiKeyClusterCommandCallback>) (client, key) -> client.smembers(key), - Arrays.asList(others)) - .resultsAsList(); + ByteArraySet values = new ByteArraySet(sMembers(source)); + Collection> resultList = connection.getClusterCommandExecutor() + .executeMultiKeyCommand( + (JedisMultiKeyClusterCommandCallback>) (client, key) -> client.smembers(key), + Arrays.asList(others)) + .resultsAsList(); - if (values.isEmpty()) { - return Collections.emptySet(); - } + if (values.isEmpty()) { + return Collections.emptySet(); + } - for (Set singleNodeValue : resultList) { - values.removeAll(singleNodeValue); - } + for (Set singleNodeValue : resultList) { + values.removeAll(singleNodeValue); + } - return values.asRawSet(); + return values.asRawSet(); + }); } @Override diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java index 6d67a448bd..046a42d61f 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterSetCommands.java @@ -26,6 +26,7 @@ import org.springframework.data.redis.connection.lettuce.LettuceClusterConnection.LettuceMultiKeyClusterCommandCallback; import org.springframework.data.redis.connection.util.ByteArraySet; import org.springframework.data.redis.util.ByteUtils; +import org.springframework.data.redis.util.KeyUtils; import org.springframework.util.Assert; /** @@ -191,24 +192,24 @@ public Set sDiff(byte[]... keys) { return super.sDiff(keys); } - byte[] source = keys[0]; - byte[][] others = Arrays.copyOfRange(keys, 1, keys.length); + return KeyUtils.splitKeys(keys, (source, others) -> { - ByteArraySet values = new ByteArraySet(sMembers(source)); - Collection> nodeResult = connection.getClusterCommandExecutor() - .executeMultiKeyCommand((LettuceMultiKeyClusterCommandCallback>) RedisSetCommands::smembers, - Arrays.asList(others)) - .resultsAsList(); + ByteArraySet values = new ByteArraySet(sMembers(source)); + Collection> nodeResult = connection.getClusterCommandExecutor() + .executeMultiKeyCommand((LettuceMultiKeyClusterCommandCallback>) RedisSetCommands::smembers, + Arrays.asList(others)) + .resultsAsList(); - if (values.isEmpty()) { - return Collections.emptySet(); - } + if (values.isEmpty()) { + return Collections.emptySet(); + } - for (Set toSubstract : nodeResult) { - values.removeAll(toSubstract); - } + for (Set toSubstract : nodeResult) { + values.removeAll(toSubstract); + } - return values.asRawSet(); + return values.asRawSet(); + }); } @Override diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java index 440814d390..d6c618db16 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveStringCommands.java @@ -22,7 +22,6 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -39,6 +38,7 @@ import org.springframework.data.redis.connection.ReactiveStringCommands; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.core.types.Expiration; +import org.springframework.data.redis.util.KeyUtils; import org.springframework.util.Assert; /** @@ -370,9 +370,12 @@ public Flux> bitOp(Publisher c Assert.isTrue(sourceKeys.length == 1, "BITOP NOT does not allow more than 1 source key."); yield reactiveCommands.bitopNot(destinationKey, sourceKeys[0]); } - case DIFF -> reactiveCommands.bitopDiff(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); - case DIFF1 -> reactiveCommands.bitopDiff1(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); - case ANDOR -> reactiveCommands.bitopAndor(destinationKey, sourceKeys[0], Arrays.copyOfRange(sourceKeys, 1, sourceKeys.length)); + case DIFF -> KeyUtils.splitKeys(sourceKeys, + (first, remaining) -> reactiveCommands.bitopDiff(destinationKey, first, remaining)); + case DIFF1 -> KeyUtils.splitKeys(sourceKeys, + (first, remaining) -> reactiveCommands.bitopDiff1(destinationKey, first, remaining)); + case ANDOR -> KeyUtils.splitKeys(sourceKeys, + (first, remaining) -> reactiveCommands.bitopAndor(destinationKey, first, remaining)); case ONE -> reactiveCommands.bitopOne(destinationKey, sourceKeys); }; diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java index a67d0ea4c5..1c6b21c69b 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceStringCommands.java @@ -18,18 +18,19 @@ import io.lettuce.core.BitFieldArgs; import io.lettuce.core.api.async.RedisStringAsyncCommands; -import java.util.Arrays; import java.util.List; import java.util.Map; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.Range; import org.springframework.data.redis.connection.BitFieldSubCommands; import org.springframework.data.redis.connection.RedisStringCommands; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.types.Expiration; +import org.springframework.data.redis.util.KeyUtils; import org.springframework.util.Assert; /** @@ -309,9 +310,9 @@ public Long bitOp(@NonNull BitOperation op, byte @NonNull [] destination, byte @ } yield it.bitopNot(destination, keys[0]); } - case DIFF -> it.bitopDiff(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); - case DIFF1 -> it.bitopDiff1(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); - case ANDOR -> it.bitopAndor(destination, keys[0], Arrays.copyOfRange(keys, 1, keys.length)); + case DIFF -> KeyUtils.splitKeys(keys, (first, remaining) -> it.bitopDiff(destination, first, remaining)); + case DIFF1 -> KeyUtils.splitKeys(keys, (first, remaining) -> it.bitopDiff1(destination, first, remaining)); + case ANDOR -> KeyUtils.splitKeys(keys, (first, remaining) -> it.bitopAndor(destination, first, remaining)); case ONE -> it.bitopOne(destination, keys); }); } diff --git a/src/main/java/org/springframework/data/redis/util/KeyUtils.java b/src/main/java/org/springframework/data/redis/util/KeyUtils.java new file mode 100644 index 0000000000..4794c70b91 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/util/KeyUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.util; + +import java.util.Arrays; + +/** + * Utility class for Redis keys. + * + * @author Mark Paluch + * @since 4.1 + */ +public abstract class KeyUtils { + + // --------------------------------------------------------------------- + // General convenience methods for working with keys + // --------------------------------------------------------------------- + + /** + * Utility method to split an array concatenated of keys into the first key and the remaining keys and invoke the + * given function. + * + * @param keys array of keys to be separated into the first one and the remaining ones. + * @param function function to be invoked with the first and remaining keys as input arguments. + * @return result of the {@link SourceKeysFunction}. + */ + public static R splitKeys(T[] keys, SourceKeysFunction function) { + + if (keys.length == 0) { + throw new IllegalArgumentException("Keys array must contain at least one element"); + } + + T firstKey = keys[0]; + T[] otherKeys = Arrays.copyOfRange(keys, 1, keys.length); + return function.apply(firstKey, otherKeys); + } + + /** + * Represents a function that accepts two arguments of the same base type and produces a result while the second + * argument is an array of {@code T}. This is similar to a {@link java.util.function.BiFunction} but restricts both + * arguments to be of the same type. Typically used in arrangements where a composite collection of keys is split into + * the first key and the remaining keys. + *

+ * This is a functional interface whose functional method is + * {@link #apply(Object, Object)}. + * + * @param the type of the first argument to the function. + * @param the type of the result of the function. + */ + public interface SourceKeysFunction { + + /** + * Applies this function to the given arguments. + * + * @param firstKey the first key argument. + * @param otherKeys the other keys function argument. + * @return the function result. + */ + R apply(T firstKey, T[] otherKeys); + + } + + // utility constructor + private KeyUtils() { + + } + +}