diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index ff638d910..52c16b1f0 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -7,6 +7,7 @@ This section briefly covers items that are new and noteworthy in the latest rele == New in Spring Data Redis 2.2 * <> +* Refined `union`/`diff`/`intersect` set-operation methods accepting a single collection of keys. [[new-in-2.1.0]] == New in Spring Data Redis 2.1 diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java index 6bd44aeae..b4b9ab635 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveSetOperations.java @@ -182,10 +182,10 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); - return createFlux(connection -> Flux.fromIterable(keys) + return createFlux(connection -> Flux.fromIterable(keys) // .map(this::rawKey) // .collectList() // - .flatMapMany(connection::sInter) + .flatMapMany(connection::sInter) // .map(this::readValue)); } @@ -226,9 +226,9 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); Assert.notNull(destKey, "Destination key must not be null!"); - return createMono(connection -> Flux.fromIterable(keys) - .map(this::rawKey) - .collectList() + return createMono(connection -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // .flatMap(rawKeys -> connection.sInterStore(rawKey(destKey), rawKeys))); } @@ -267,10 +267,10 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); - return createFlux(connection -> Flux.fromIterable(keys) - .map(this::rawKey) - .collectList() - .flatMapMany(connection::sUnion) + return createFlux(connection -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::sUnion) // .map(this::readValue)); } @@ -312,9 +312,9 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); Assert.notNull(destKey, "Destination key must not be null!"); - return createMono(connection -> Flux.fromIterable(keys) - .map(this::rawKey) - .collectList() + return createMono(connection -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // .flatMap(rawKeys -> connection.sUnionStore(rawKey(destKey), rawKeys))); } @@ -353,10 +353,10 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); - return createFlux(connection -> Flux.fromIterable(keys) - .map(this::rawKey) - .collectList() - .flatMapMany(connection::sDiff) + return createFlux(connection -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // + .flatMapMany(connection::sDiff) // .map(this::readValue)); } @@ -398,9 +398,9 @@ class DefaultReactiveSetOperations implements ReactiveSetOperations Assert.notNull(keys, "Keys must not be null!"); Assert.notNull(destKey, "Destination key must not be null!"); - return createMono(connection -> Flux.fromIterable(keys) - .map(this::rawKey) - .collectList() + return createMono(connection -> Flux.fromIterable(keys) // + .map(this::rawKey) // + .collectList() // .flatMap(rawKeys -> connection.sDiffStore(rawKey(destKey), rawKeys))); } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java index 67444fd99..2a51617f1 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultSetOperations.java @@ -65,6 +65,7 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Set difference(K key, Collection otherKeys) { + byte[][] rawKeys = rawKeys(key, otherKeys); Set rawValues = execute(connection -> connection.sDiff(rawKeys), true); @@ -77,6 +78,7 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Set difference(Collection keys) { + byte[][] rawKeys = rawKeys(keys); Set rawValues = execute(connection -> connection.sDiff(rawKeys), true); @@ -101,6 +103,7 @@ class DefaultSetOperations extends AbstractOperations implements Set byte[][] rawKeys = rawKeys(key, otherKeys); byte[] rawDestKey = rawKey(destKey); + return execute(connection -> connection.sDiffStore(rawDestKey, rawKeys), true); } @@ -110,8 +113,10 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Long differenceAndStore(Collection keys, K destKey) { + byte[][] rawKeys = rawKeys(keys); byte[] rawDestKey = rawKey(destKey); + return execute(connection -> connection.sDiffStore(rawDestKey, rawKeys), true); } @@ -143,10 +148,12 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Set intersect(Collection keys) { + byte[][] rawKeys = rawKeys(keys); Set rawValues = execute(connection -> connection.sInter(rawKeys), true); - return deserializeValues(rawValues); } + return deserializeValues(rawValues); + } /* * (non-Javadoc) @@ -166,6 +173,7 @@ class DefaultSetOperations extends AbstractOperations implements Set byte[][] rawKeys = rawKeys(key, otherKeys); byte[] rawDestKey = rawKey(destKey); + return execute(connection -> connection.sInterStore(rawDestKey, rawKeys), true); } @@ -175,9 +183,12 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Long intersectAndStore(Collection keys, K destKey) { + byte[][] rawKeys = rawKeys(keys); byte[] rawDestKey = rawKey(destKey); - return execute(connection -> connection.sInterStore(rawDestKey, rawKeys), true); } + + return execute(connection -> connection.sInterStore(rawDestKey, rawKeys), true); + } /* * (non-Javadoc) @@ -188,6 +199,7 @@ class DefaultSetOperations extends AbstractOperations implements Set byte[] rawKey = rawKey(key); byte[] rawValue = rawValue(o); + return execute(connection -> connection.sIsMember(rawKey, rawValue), true); } @@ -346,6 +358,7 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Set union(Collection keys) { + byte[][] rawKeys = rawKeys(keys); Set rawValues = execute(connection -> connection.sUnion(rawKeys), true); @@ -370,6 +383,7 @@ class DefaultSetOperations extends AbstractOperations implements Set byte[][] rawKeys = rawKeys(key, otherKeys); byte[] rawDestKey = rawKey(destKey); + return execute(connection -> connection.sUnionStore(rawDestKey, rawKeys), true); } @@ -379,9 +393,12 @@ class DefaultSetOperations extends AbstractOperations implements Set */ @Override public Long unionAndStore(Collection keys, K destKey) { + byte[][] rawKeys = rawKeys(keys); byte[] rawDestKey = rawKey(destKey); - return execute(connection -> connection.sUnionStore(rawDestKey, rawKeys), true); } + + return execute(connection -> connection.sUnionStore(rawDestKey, rawKeys), true); + } /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java index 2fd3d71db..f766df24f 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveSetOperations.java @@ -126,6 +126,7 @@ public interface ReactiveSetOperations { * @param keys must not be {@literal null}. * @return * @see Redis Documentation: SINTER + * @since 2.2 */ Flux intersect(Collection keys); @@ -158,6 +159,7 @@ public interface ReactiveSetOperations { * @param destKey must not be {@literal null}. * @return * @see Redis Documentation: SINTERSTORE + * @since 2.2 */ Mono intersectAndStore(Collection keys, K destKey); @@ -187,6 +189,7 @@ public interface ReactiveSetOperations { * @param keys must not be {@literal null}. * @return * @see Redis Documentation: SUNION + * @since 2.2 */ Flux union(Collection keys); @@ -219,6 +222,7 @@ public interface ReactiveSetOperations { * @param destKey must not be {@literal null}. * @return * @see Redis Documentation: SUNIONSTORE + * @since 2.2 */ Mono unionAndStore(Collection keys, K destKey); @@ -248,6 +252,7 @@ public interface ReactiveSetOperations { * @param keys must not be {@literal null}. * @return * @see Redis Documentation: SDIFF + * @since 2.2 */ Flux difference(Collection keys); @@ -280,6 +285,7 @@ public interface ReactiveSetOperations { * @param destKey must not be {@literal null}. * @return * @see Redis Documentation: SDIFFSTORE + * @since 2.2 */ Mono differenceAndStore(Collection keys, K destKey); diff --git a/src/main/java/org/springframework/data/redis/core/SetOperations.java b/src/main/java/org/springframework/data/redis/core/SetOperations.java index ebd564527..06eebbd98 100644 --- a/src/main/java/org/springframework/data/redis/core/SetOperations.java +++ b/src/main/java/org/springframework/data/redis/core/SetOperations.java @@ -136,6 +136,7 @@ public interface SetOperations { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTER + * @since 2.2 */ @Nullable Set intersect(Collection keys); @@ -171,6 +172,7 @@ public interface SetOperations { * @param destKey must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SINTERSTORE + * @since 2.2 */ @Nullable Long intersectAndStore(Collection keys, K destKey); @@ -203,6 +205,7 @@ public interface SetOperations { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SUNION + * @since 2.2 */ @Nullable Set union(Collection keys); @@ -238,6 +241,7 @@ public interface SetOperations { * @param destKey must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SUNIONSTORE + * @since 2.2 */ @Nullable Long unionAndStore(Collection keys, K destKey); @@ -270,6 +274,7 @@ public interface SetOperations { * @param keys must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SDIFF + * @since 2.2 */ @Nullable Set difference(Collection keys); @@ -305,6 +310,7 @@ public interface SetOperations { * @param destKey must not be {@literal null}. * @return {@literal null} when used in pipeline / transaction. * @see Redis Documentation: SDIFFSTORE + * @since 2.2 */ @Nullable Long differenceAndStore(Collection keys, K destKey); diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java index ea6345f35..4b76605f1 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveSetOperationsIntegrationTests.java @@ -20,6 +20,7 @@ import static org.junit.Assume.*; import reactor.test.StepVerifier; +import java.util.Arrays; import java.util.Collection; import org.junit.AfterClass; @@ -171,7 +172,7 @@ public class DefaultReactiveSetOperationsIntegrationTests { StepVerifier.create(setOperations.isMember(key, value1)).expectNext(true).verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void intersect() { assumeFalse(valueFactory instanceof ByteBufferObjectFactory); @@ -191,9 +192,15 @@ public class DefaultReactiveSetOperationsIntegrationTests { assertThat(actual).isEqualTo(shared); }) // .verifyComplete(); + + StepVerifier.create(setOperations.intersect(Arrays.asList(key, otherKey))) // + .consumeNextWith(actual -> { + assertThat(actual).isEqualTo(shared); + }) // + .verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void intersectAndStore() { K key = keyFactory.instance(); @@ -211,9 +218,16 @@ public class DefaultReactiveSetOperationsIntegrationTests { .verify(); StepVerifier.create(setOperations.isMember(destKey, shared)).expectNext(true).verifyComplete(); + + StepVerifier.create(setOperations.delete(destKey)).expectNext(true).verifyComplete(); + + StepVerifier.create(setOperations.intersectAndStore(Arrays.asList(key, otherKey), destKey)).expectNext(1L) + .expectComplete().verify(); + + StepVerifier.create(setOperations.isMember(destKey, shared)).expectNext(true).verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void difference() { assumeFalse(valueFactory instanceof ByteBufferObjectFactory); @@ -233,9 +247,15 @@ public class DefaultReactiveSetOperationsIntegrationTests { assertThat(actual).isEqualTo(onlyInKey); }) // .verifyComplete(); + + StepVerifier.create(setOperations.difference(Arrays.asList(key, otherKey))) // + .consumeNextWith(actual -> { + assertThat(actual).isEqualTo(onlyInKey); + }) // + .verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void differenceAndStore() { K key = keyFactory.instance(); @@ -252,10 +272,13 @@ public class DefaultReactiveSetOperationsIntegrationTests { StepVerifier.create(setOperations.differenceAndStore(key, otherKey, destKey)).expectNext(1L).expectComplete() .verify(); + StepVerifier.create(setOperations.differenceAndStore(Arrays.asList(key, otherKey), destKey)).expectNext(1L) + .expectComplete().verify(); + StepVerifier.create(setOperations.isMember(destKey, onlyInKey)).expectNext(true).verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void union() { assumeFalse(valueFactory instanceof ByteBufferObjectFactory); @@ -273,9 +296,13 @@ public class DefaultReactiveSetOperationsIntegrationTests { StepVerifier.create(setOperations.union(key, otherKey)) // .expectNextCount(3) // .verifyComplete(); + + StepVerifier.create(setOperations.union(Arrays.asList(key, otherKey))) // + .expectNextCount(3) // + .verifyComplete(); } - @Test // DATAREDIS-602 + @Test // DATAREDIS-602, DATAREDIS-873 public void unionAndStore() { K key = keyFactory.instance(); @@ -291,6 +318,11 @@ public class DefaultReactiveSetOperationsIntegrationTests { StepVerifier.create(setOperations.unionAndStore(key, otherKey, destKey)).expectNext(3L).verifyComplete(); + StepVerifier.create(setOperations.delete(destKey)).expectNext(true).verifyComplete(); + + StepVerifier.create(setOperations.unionAndStore(Arrays.asList(key, otherKey), destKey)).expectNext(3L) + .verifyComplete(); + StepVerifier.create(setOperations.isMember(destKey, onlyInKey)).expectNext(true).verifyComplete(); StepVerifier.create(setOperations.isMember(destKey, shared)).expectNext(true).verifyComplete(); StepVerifier.create(setOperations.isMember(destKey, onlyInOtherKey)).expectNext(true).verifyComplete(); diff --git a/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsTests.java b/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsTests.java index d38561262..3f8d8d4b7 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultSetOperationsTests.java @@ -22,6 +22,7 @@ import static org.junit.Assume.*; import static org.springframework.data.redis.matcher.RedisTestMatchers.*; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -49,6 +50,7 @@ import org.springframework.test.annotation.IfProfileValue; * @author Jennifer Hickey * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ @RunWith(Parameterized.class) public class DefaultSetOperationsTests { @@ -243,7 +245,94 @@ public class DefaultSetOperationsTests { assertThat(count, is(setOps.size(key))); } - @Test // DATAREDIS-448 + @Test // DATAREDIS-873 + public void diffShouldReturnDifference() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + + assertThat(setOps.difference(Arrays.asList(sourceKey1, sourceKey2)), hasItems(v1)); + } + + @Test // DATAREDIS-873 + public void diffAndStoreShouldReturnDifferenceShouldReturnNumberOfElementsInDestination() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + K destinationKey = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + + assertThat(setOps.differenceAndStore(Arrays.asList(sourceKey1, sourceKey2), destinationKey), isEqual(1L)); + } + + @Test // DATAREDIS-873 + public void unionShouldConcatSets() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + + assertThat(setOps.union(Arrays.asList(sourceKey1, sourceKey2)), hasItems(v1, v2, v3, v4)); + } + + @Test // DATAREDIS-873 + public void unionAndStoreShouldReturnDifferenceShouldReturnNumberOfElementsInDestination() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + K destinationKey = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + + assertThat(setOps.unionAndStore(Arrays.asList(sourceKey1, sourceKey2), destinationKey), isEqual(4L)); + } + + @Test // DATAREDIS-873 + public void intersectShouldReturnElements() { + + K sourceKey1 = keyFactory.instance(); + K sourceKey2 = keyFactory.instance(); + + V v1 = valueFactory.instance(); + V v2 = valueFactory.instance(); + V v3 = valueFactory.instance(); + V v4 = valueFactory.instance(); + + setOps.add(sourceKey1, v1, v2, v3); + setOps.add(sourceKey2, v2, v3, v4); + + assertThat(setOps.intersect(Arrays.asList(sourceKey1, sourceKey2)), hasSize(2)); + } + + @Test // DATAREDIS-448, DATAREDIS-873 public void intersectAndStoreShouldReturnNumberOfElementsInDestination() { K sourceKey1 = keyFactory.instance(); @@ -259,5 +348,6 @@ public class DefaultSetOperationsTests { setOps.add(sourceKey2, v2, v3, v4); assertThat(setOps.intersectAndStore(sourceKey1, sourceKey2, destinationKey), is(2L)); + assertThat(setOps.intersectAndStore(Arrays.asList(sourceKey1, sourceKey2), destinationKey), is(2L)); } }