DATAREDIS-746 - Add Zset intersect operation aggregation support.

We now support aggregation and weight options for ZINTERSTORE. We also encapsulate weights used for ZUNIONSTORE and ZINTERSTORE within a Weights value object.

zSetOps.intersectAndStore(set1, Arrays.asList(set2, set3), out, Aggregate.MAX,
				Weights.of(1, 2, 1));

Related ticket: DATAREDIS-515.

Original Pull Request: #314
This commit is contained in:
Mark Paluch
2018-02-16 09:45:07 +01:00
committed by Christoph Strobl
parent 7d6524bace
commit 56a01625d5
18 changed files with 802 additions and 54 deletions

View File

@@ -15,17 +15,8 @@
*/
package org.springframework.data.redis.connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
@@ -1313,6 +1304,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.zInterStore(destKey, aggregate, weights, sets), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
return convertAndReturn(delegate.zInterStore(destKey, aggregate, weights, sets), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], byte[][])
@@ -1573,6 +1573,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.zUnionStore(destKey, aggregate, weights, sets), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
return convertAndReturn(delegate.zUnionStore(destKey, aggregate, weights, sets), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], byte[][])

View File

@@ -685,6 +685,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
return zSetCommands().zInterStore(destKey, aggregate, weights, sets);
}
/** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */
@Override
@Deprecated
default Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
return zSetCommands().zInterStore(destKey, aggregate, weights, sets);
}
/** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */
@Override
@Deprecated
@@ -811,6 +818,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
return zSetCommands().zUnionStore(destKey, aggregate, weights, sets);
}
/** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */
@Override
@Deprecated
default Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
return zSetCommands().zUnionStore(destKey, aggregate, weights, sets);
}
/** @deprecated in favor of {@link RedisConnection#zSetCommands()}}. */
@Override
@Deprecated

View File

@@ -34,6 +34,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Numeric
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -1366,6 +1367,17 @@ public interface ReactiveZSetCommands {
return new ZUnionStoreCommand(getKey(), sourceKeys, weights, aggregateFunction);
}
/**
* Applies the {@link Weights}. Constructs a new command instance with all previously configured properties.
*
* @param weights must not be {@literal null}.
* @return a new {@link ZUnionStoreCommand} with {@literal weights} applied.
* @since 2.1
*/
public ZUnionStoreCommand applyWeights(Weights weights) {
return new ZUnionStoreCommand(getKey(), sourceKeys, weights.toList(), aggregateFunction);
}
/**
* Applies a specific {@link Aggregate} function. Constructs a new command instance with all previously configured
* properties.
@@ -1440,6 +1452,21 @@ public interface ReactiveZSetCommands {
return zUnionStore(destinationKey, sets, weights, null);
}
/**
* Union sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to
* individual sets.
*
* @param destinationKey must not be {@literal null}.
* @param sets must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
default Mono<Long> zUnionStore(ByteBuffer destinationKey, List<ByteBuffer> sets, Weights weights) {
return zUnionStore(destinationKey, sets, weights, null);
}
/**
* Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.
@@ -1462,6 +1489,29 @@ public interface ReactiveZSetCommands {
.next().map(NumericResponse::getOutput);
}
/**
* Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.
*
* @param destinationKey must not be {@literal null}.
* @param sets must not be {@literal null}.
* @param weights can be {@literal null}.
* @param aggregateFunction can be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
default Mono<Long> zUnionStore(ByteBuffer destinationKey, List<ByteBuffer> sets, Weights weights,
@Nullable Aggregate aggregateFunction) {
Assert.notNull(destinationKey, "DestinationKey must not be null!");
Assert.notNull(sets, "Sets must not be null!");
return zUnionStore(Mono.just(
ZUnionStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey)))
.next().map(NumericResponse::getOutput);
}
/**
* Union sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.
@@ -1517,6 +1567,17 @@ public interface ReactiveZSetCommands {
return new ZInterStoreCommand(getKey(), sourceKeys, weights, aggregateFunction);
}
/**
* Applies the {@link Weights}. Constructs a new command instance with all previously configured properties.
*
* @param weights must not be {@literal null}.
* @return a new {@link ZInterStoreCommand} with {@literal weights} applied.
* @since 2.1
*/
public ZInterStoreCommand applyWeights(Weights weights) {
return new ZInterStoreCommand(getKey(), sourceKeys, weights.toList(), aggregateFunction);
}
/**
* Applies a specific {@link Aggregate} function. Constructs a new command instance with all previously configured
* properties.
@@ -1591,6 +1652,21 @@ public interface ReactiveZSetCommands {
return zInterStore(destinationKey, sets, weights, null);
}
/**
* Intersect sorted {@literal sets} and store result in destination {@literal destinationKey} and apply weights to
* individual sets.
*
* @param destinationKey must not be {@literal null}.
* @param sets must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
default Mono<Long> zInterStore(ByteBuffer destinationKey, List<ByteBuffer> sets, Weights weights) {
return zInterStore(destinationKey, sets, weights, null);
}
/**
* Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.
@@ -1613,6 +1689,29 @@ public interface ReactiveZSetCommands {
.next().map(NumericResponse::getOutput);
}
/**
* Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.
*
* @param destinationKey must not be {@literal null}.
* @param sets must not be {@literal null}.
* @param weights must not be {@literal null}.
* @param aggregateFunction can be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
default Mono<Long> zInterStore(ByteBuffer destinationKey, List<ByteBuffer> sets, Weights weights,
@Nullable Aggregate aggregateFunction) {
Assert.notNull(destinationKey, "DestinationKey must not be null!");
Assert.notNull(sets, "Sets must not be null!");
return zInterStore(Mono.just(
ZInterStoreCommand.sets(sets).aggregateUsing(aggregateFunction).applyWeights(weights).storeAs(destinationKey)))
.next().map(NumericResponse::getOutput);
}
/**
* Intersect sorted {@literal sets} by applying {@literal aggregateFunction} and store result in destination
* {@literal destinationKey} and apply weights to individual sets.

View File

@@ -15,7 +15,13 @@
*/
package org.springframework.data.redis.connection;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.DoubleUnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
@@ -40,6 +46,128 @@ public interface RedisZSetCommands {
SUM, MIN, MAX;
}
/**
* Value object encapsulating a multiplication factor for each input sorted set. This means that the score of every
* element in every input sorted set is multiplied by this factor before being passed to the aggregation function.
*
* @author Mark Paluch
* @since 2.1
*/
class Weights {
private final double[] weights;
Weights(double[] weights) {
this.weights = weights;
}
/**
* Create new {@link Weights} given {@code weights} as {@code int}.
*
* @param weights must not be {@literal null}.
* @return the {@link Weights} for {@code weights}.
*/
public static Weights of(int... weights) {
Assert.notNull(weights, "Weights must not be null!");
return new Weights(Arrays.stream(weights).mapToDouble(value -> value).toArray());
}
/**
* Create new {@link Weights} given {@code weights} as {@code double}.
*
* @param weights must not be {@literal null}.
* @return the {@link Weights} for {@code weights}.
*/
public static Weights of(double... weights) {
Assert.notNull(weights, "Weights must not be null!");
return new Weights(Arrays.copyOf(weights, weights.length));
}
/**
* Creates equal {@link Weights} for a number of input sets {@code count} with a weight of one.
*
* @param count number of input sets. Must be greater or equal to zero.
* @return equal {@link Weights} for a number of input sets with a weight of one.
*/
public static Weights fromSetCount(int count) {
Assert.isTrue(count >= 0, "Count of input sorted sets must be greater or equal to zero!");
return new Weights(IntStream.range(0, count).mapToDouble(value -> 1).toArray());
}
/**
* Creates a new {@link Weights} object that contains all weights multiplied by {@code multiplier}
*
* @param multiplier multiplier used to multiply each weight with.
* @return equal {@link Weights} for a number of input sets with a weight of one.
*/
public Weights multiply(int multiplier) {
return apply(it -> it * multiplier);
}
/**
* Creates a new {@link Weights} object that contains all weights multiplied by {@code multiplier}
*
* @param multiplier multiplier used to multiply each weight with.
* @return equal {@link Weights} for a number of input sets with a weight of one.
*/
public Weights multiply(double multiplier) {
return apply(it -> it * multiplier);
}
/**
* Creates a new {@link Weights} object that contains all weights with {@link DoubleUnaryOperator} applied.
*
* @param operator operator function.
* @return the new {@link Weights} with {@link DoubleUnaryOperator} applied.
*/
public Weights apply(DoubleUnaryOperator operator) {
return new Weights(DoubleStream.of(weights).map(operator).toArray());
}
/**
* Retrieve the weight at {@code index}.
*
* @param index the weight index.
* @return the weight at {@code index}.
* @throws IndexOutOfBoundsException if the index is out of range
*/
public double getWeight(int index) {
if (index > size() || index < 0) {
throw new IndexOutOfBoundsException("No such weight");
}
return weights[index];
}
/**
* @return number of weights.
*/
public int size() {
return weights.length;
}
/**
* @return an array containing all of the weights in this list in proper sequence (from first to last element).
*/
public double[] toArray() {
return Arrays.copyOf(weights, weights.length);
}
/**
* @return a {@link List} containing all of the weights in this list in proper sequence (from first to last
* element).
*/
public List<Double> toList() {
return Arrays.stream(weights).boxed().collect(Collectors.toList());
}
}
/**
* ZSet tuple.
*/
@@ -687,7 +815,7 @@ public interface RedisZSetCommands {
*
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights
* @param weights must not be {@literal null}.
* @param sets must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
@@ -695,6 +823,20 @@ public interface RedisZSetCommands {
@Nullable
Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets);
/**
* Union sorted {@code sets} and store result in destination {@code key}.
*
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @param sets must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
@Nullable
Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets);
/**
* Intersect sorted {@code sets} and store result in destination {@code key}.
*
@@ -719,6 +861,20 @@ public interface RedisZSetCommands {
@Nullable
Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets);
/**
* Intersect sorted {@code sets} and store result in destination {@code key}.
*
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @param sets must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
@Nullable
Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets);
/**
* Use a {@link Cursor} to iterate over elements in sorted set at {@code key}.
*

View File

@@ -631,16 +631,28 @@ class JedisClusterZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zUnionStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
ZParams zparams = new ZParams().weights(weights).aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
ZParams zparams = new ZParams().weightsByDouble(weights.toArray())
.aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
try {
return connection.getCluster().zunionstore(destKey, zparams, sets);
@@ -683,16 +695,28 @@ class JedisClusterZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zInterStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
byte[][] allKeys = ByteUtils.mergeArrays(destKey, sets);
if (ClusterSlotHashUtil.isSameSlotForAllKeys(allKeys)) {
ZParams zparams = new ZParams().weights(weights).aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
ZParams zparams = new ZParams().weightsByDouble(weights.toArray())
.aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
try {
return connection.getCluster().zinterstore(destKey, zparams, sets);

View File

@@ -585,13 +585,26 @@ class JedisZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zUnionStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.notNull(weights, "Weights must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
try {
ZParams zparams = new ZParams().weights(weights).aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
ZParams zparams = new ZParams().weightsByDouble(weights.toArray())
.aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
if (isPipelined()) {
pipeline(connection.newJedisResult(connection.getRequiredPipeline().zunionstore(destKey, zparams, sets)));
@@ -639,13 +652,25 @@ class JedisZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zInterStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
try {
ZParams zparams = new ZParams().weights(weights).aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
ZParams zparams = new ZParams().weightsByDouble(weights.toArray())
.aggregate(ZParams.Aggregate.valueOf(aggregate.name()));
if (isPipelined()) {
pipeline(connection.newJedisResult(connection.getRequiredPipeline().zinterstore(destKey, zparams, sets)));

View File

@@ -39,6 +39,7 @@ import org.springframework.data.redis.connection.ReactiveZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -466,7 +467,7 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
}));
}
private ZStoreArgs zStoreArgs(Aggregate aggregate, List<Double> weights) {
private static ZStoreArgs zStoreArgs(@Nullable Aggregate aggregate, @Nullable List<Double> weights) {
ZStoreArgs args = new ZStoreArgs();
if (aggregate != null) {
@@ -484,12 +485,9 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
}
if (weights != null) {
double[] lg = new double[weights.size()];
for (int i = 0; i < lg.length; i++) {
lg[i] = weights.get(i).longValue();
}
args.weights(lg);
args.weights(weights.stream().mapToDouble(it -> it).toArray());
}
return args;
}

View File

@@ -33,6 +33,7 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.KeyBoundCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -574,10 +575,21 @@ class LettuceZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zUnionStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zUnionStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zUnionStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
ZStoreArgs storeArgs = zStoreArgs(aggregate, weights);
@@ -628,10 +640,21 @@ class LettuceZSetCommands implements RedisZSetCommands {
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, int[] weights, byte[]... sets) {
return zInterStore(destKey, aggregate, Weights.of(weights), sets);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisZSetCommands#zInterStore(byte[], org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights, byte[][])
*/
@Override
public Long zInterStore(byte[] destKey, Aggregate aggregate, Weights weights, byte[]... sets) {
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(sets, "Source sets must not be null!");
Assert.noNullElements(sets, "Source sets must not contain null elements!");
Assert.isTrue(weights.size() == sets.length, () -> String
.format("The number of weights (%d) must match the number of source sets (%d)!", weights.size(), sets.length));
ZStoreArgs storeArgs = zStoreArgs(aggregate, weights);
@@ -898,8 +921,10 @@ class LettuceZSetCommands implements RedisZSetCommands {
return connection.convertLettuceAccessException(ex);
}
private ZStoreArgs zStoreArgs(Aggregate aggregate, int[] weights) {
private static ZStoreArgs zStoreArgs(@Nullable Aggregate aggregate, Weights weights) {
ZStoreArgs args = new ZStoreArgs();
if (aggregate != null) {
switch (aggregate) {
case MIN:
@@ -913,11 +938,9 @@ class LettuceZSetCommands implements RedisZSetCommands {
break;
}
}
double[] lg = new double[weights.length];
for (int i = 0; i < lg.length; i++) {
lg[i] = weights[i];
}
args.weights(lg);
args.weights(weights.toArray());
return args;
}

View File

@@ -22,6 +22,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.lang.Nullable;
@@ -278,13 +279,13 @@ public interface BoundZSetOperations<K, V> extends BoundKeyOperations<K> {
* Union sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param otherKeys must not be {@literal null}.
* @param weights must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
void unionAndStore(Collection<K> otherKeys, int[] weights, K destKey, Aggregate aggregate);
void unionAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Intersect sorted sets at the bound key and {@code otherKey} and store result in destination {@code destKey}.
@@ -304,6 +305,29 @@ public interface BoundZSetOperations<K, V> extends BoundKeyOperations<K> {
*/
void intersectAndStore(Collection<K> otherKeys, K destKey);
/**
* Intersect sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
void intersectAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate);
/**
* Intersect sorted sets at the bound key and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
void intersectAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Iterate over elements in zset at the bound key. <br />
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.

View File

@@ -20,9 +20,10 @@ import java.util.Collection;
import java.util.Set;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
/**
@@ -103,6 +104,24 @@ class DefaultBoundZSetOperations<K, V> extends DefaultBoundKeyOperations<K> impl
ops.intersectAndStore(getKey(), otherKeys, destKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public void intersectAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate) {
ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundZSetOperations#intersectAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public void intersectAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
ops.intersectAndStore(getKey(), otherKeys, destKey, aggregate, weights);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundZSetOperations#range(long, long)
@@ -292,22 +311,22 @@ class DefaultBoundZSetOperations<K, V> extends DefaultBoundKeyOperations<K> impl
ops.unionAndStore(getKey(), otherKeys, destKey);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundZSetOperations#unionAndStore(Collection, Object, RedisZSetCommands.Aggregate)
* @see org.springframework.data.redis.core.BoundZSetOperations#unionAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public void unionAndStore(Collection<K> otherKeys, K destKey, RedisZSetCommands.Aggregate aggregate) {
public void unionAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate) {
ops.unionAndStore(getKey(), otherKeys, destKey, aggregate);
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundZSetOperations#unionAndStore(Collection, int[], Object, RedisZSetCommands.Aggregate)
* @see org.springframework.data.redis.core.BoundZSetOperations#unionAndStore(java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public void unionAndStore(Collection<K> otherKeys, int[] weights, K destKey, RedisZSetCommands.Aggregate aggregate) {
ops.unionAndStore(getKey(), otherKeys, weights, destKey, aggregate);
public void unionAndStore(Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
ops.unionAndStore(getKey(), otherKeys, destKey, aggregate, weights);
}
/*

View File

@@ -31,8 +31,10 @@ import org.reactivestreams.Publisher;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.DefaultTuple;
import org.springframework.data.redis.connection.ReactiveZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.util.ByteUtils;
@@ -340,7 +342,8 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
return createMono(connection -> connection.zRemRangeByScore(rawKey(key), range));
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.lang.Object, java.lang.Object)
*/
@Override
@@ -353,7 +356,8 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
return unionAndStore(key, Collections.singleton(otherKey), destKey);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object)
*/
@Override
@@ -369,7 +373,39 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
.flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized)));
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public Mono<Long> unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate) {
Assert.notNull(otherKeys, "Other keys must not be null!");
return unionAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public Mono<Long> unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(otherKeys, "Other keys must not be null!");
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(aggregate, "Aggregate must not be null!");
Assert.notNull(weights, "Weights must not be null!");
return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) //
.map(this::rawKey) //
.collectList() //
.flatMap(serialized -> connection.zUnionStore(rawKey(destKey), serialized, weights, aggregate)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.lang.Object, java.lang.Object)
*/
@Override
@@ -382,7 +418,8 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
return intersectAndStore(key, Collections.singleton(otherKey), destKey);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object)
*/
@Override
@@ -398,6 +435,37 @@ class DefaultReactiveZSetOperations<K, V> implements ReactiveZSetOperations<K, V
.flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public Mono<Long> intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate) {
Assert.notNull(otherKeys, "Other keys must not be null!");
return intersectAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public Mono<Long> intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(otherKeys, "Other keys must not be null!");
Assert.notNull(destKey, "Destination key must not be null!");
Assert.notNull(aggregate, "Aggregate must not be null!");
Assert.notNull(weights, "Weights must not be null!");
return createMono(connection -> Flux.fromIterable(getKeys(key, otherKeys)) //
.map(this::rawKey) //
.collectList() //
.flatMap(serialized -> connection.zInterStore(rawKey(destKey), serialized, weights, aggregate)));
}
/* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveZSetOperations#rangeByLex(java.lang.Object, org.springframework.data.domain.Range)
*/

View File

@@ -19,10 +19,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
/**
* Default implementation of {@link ZSetOperations}.
@@ -94,9 +95,32 @@ class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZS
byte[][] rawKeys = rawKeys(key, otherKeys);
byte[] rawDestKey = rawKey(destKey);
return execute(connection -> connection.zInterStore(rawDestKey, rawKeys), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate) {
return intersectAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ZSetOperations#intersectAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
byte[][] rawKeys = rawKeys(key, otherKeys);
byte[] rawDestKey = rawKey(destKey);
return execute(connection -> connection.zInterStore(rawDestKey, aggregate, weights, rawKeys), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ZSetOperations#range(java.lang.Object, long, long)
@@ -402,8 +426,10 @@ class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZS
*/
@Override
public Long unionAndStore(K key, Collection<K> otherKeys, K destKey) {
byte[][] rawKeys = rawKeys(key, otherKeys);
byte[] rawDestKey = rawKey(destKey);
return execute(connection -> connection.zUnionStore(rawDestKey, rawKeys), true);
}
@@ -412,22 +438,20 @@ class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZS
* @see org.springframework.data.redis.core.ZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
*/
@Override
public Long unionAndStore(K key, Collection<K> otherKeys, K destKey, RedisZSetCommands.Aggregate aggregate) {
int weights[] = new int[otherKeys.size() + (key != null ? 1 : 0)];
for (int i = 0; i < weights.length; i++) {
weights[i] = 1;
}
return unionAndStore(key, otherKeys, weights, destKey, aggregate);
public Long unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate) {
return unionAndStore(key, otherKeys, destKey, aggregate, Weights.fromSetCount(1 + otherKeys.size()));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, int[], java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate)
* @see org.springframework.data.redis.core.ZSetOperations#unionAndStore(java.lang.Object, java.util.Collection, java.lang.Object, org.springframework.data.redis.connection.RedisZSetCommands.Aggregate, org.springframework.data.redis.connection.RedisZSetCommands.Weights)
*/
@Override
public Long unionAndStore(K key, Collection<K> otherKeys, int[] weights, K destKey, RedisZSetCommands.Aggregate aggregate) {
public Long unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights) {
byte[][] rawKeys = rawKeys(key, otherKeys);
byte[] rawDestKey = rawKey(destKey);
return execute(connection -> connection.zUnionStore(rawDestKey, aggregate, weights, rawKeys), true);
}

View File

@@ -21,8 +21,10 @@ import reactor.core.publisher.Mono;
import java.util.Collection;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
/**
@@ -297,6 +299,33 @@ public interface ReactiveZSetOperations<K, V> {
*/
Mono<Long> unionAndStore(K key, Collection<K> otherKeys, K destKey);
/**
* Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
Mono<Long> unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate);
/**
* Union sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
Mono<Long> unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Intersect sorted sets at {@code key} and {@code otherKey} and store result in destination {@code destKey}.
*
@@ -319,6 +348,33 @@ public interface ReactiveZSetOperations<K, V> {
*/
Mono<Long> intersectAndStore(K key, Collection<K> otherKeys, K destKey);
/**
* Intersect sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
Mono<Long> intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate);
/**
* Intersect sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
Mono<Long> intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Get all elements with lexicographical ordering from {@literal ZSET} at {@code key} with a value between
* {@link Range#getLowerBound()} and {@link Range#getUpperBound()}.

View File

@@ -22,6 +22,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Aggregate;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.RedisZSetCommands.Weights;
import org.springframework.lang.Nullable;
/**
@@ -384,15 +385,15 @@ public interface ZSetOperations<K, V> {
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param weights must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
* @see <a href="http://redis.io/commands/zunionstore">Redis Documentation: ZUNIONSTORE</a>
*/
@Nullable
Long unionAndStore(K key, Collection<K> otherKeys, int[] weights, K destKey, Aggregate aggregate);
Long unionAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Intersect sorted sets at {@code key} and {@code otherKey} and store result in destination {@code destKey}.
@@ -418,6 +419,35 @@ public interface ZSetOperations<K, V> {
@Nullable
Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);
/**
* Intersect sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
@Nullable
Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate);
/**
* Intersect sorted sets at {@code key} and {@code otherKeys} and store result in destination {@code destKey}.
*
* @param key must not be {@literal null}.
* @param otherKeys must not be {@literal null}.
* @param destKey must not be {@literal null}.
* @param aggregate must not be {@literal null}.
* @param weights must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
* @see <a href="http://redis.io/commands/zinterstore">Redis Documentation: ZINTERSTORE</a>
*/
@Nullable
Long intersectAndStore(K key, Collection<K> otherKeys, K destKey, Aggregate aggregate, Weights weights);
/**
* Iterate over elements in zset at {@code key}. <br />
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.