DATAREDIS-1196 - Add support for LPOS command.

Original pull request: #563.
This commit is contained in:
Christoph Strobl
2020-09-15 09:47:59 +02:00
committed by Mark Paluch
parent 1a025fea46
commit 76cbe5124d
22 changed files with 718 additions and 11 deletions

View File

@@ -702,6 +702,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.lPop(key), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#lPos(byte[], byte[], java.lang.Integer, java.lang.Integer)
*/
@Override
public List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count) {
return convertAndReturn(delegate.lPos(key, element, rank, count), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#lPush(byte[], byte[][])
@@ -2138,6 +2147,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.lPop(serialize(key)), bytesToString);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#lPos(java.lang.String, java.lang.String, java.lang.Integer, java.lang.Integer)
*/
@Override
public List<Long> lPos(String key, String element, @Nullable Integer rank, @Nullable Integer count) {
return lPos(serialize(key), serialize(element), rank, count);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#lPush(java.lang.String, java.lang.String[])

View File

@@ -621,6 +621,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
return listCommands().rPush(key, values);
}
/** @deprecated in favor of {@link RedisConnection#listCommands()}}. */
@Override
@Deprecated
default List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count) {
return listCommands().lPos(key, element, rank, count);
}
/** @deprecated in favor of {@link RedisConnection#listCommands()}}. */
@Override
@Deprecated

View File

@@ -312,6 +312,125 @@ public interface ReactiveListCommands {
*/
Flux<BooleanResponse<RangeCommand>> lTrim(Publisher<RangeCommand> commands);
/**
* {@code LPOS} command parameters.
*
* @author Christoph Strobl
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
class LPosCommand extends KeyCommand {
private final ByteBuffer element;
private final @Nullable Integer count;
private final @Nullable Integer rank;
private LPosCommand(@Nullable ByteBuffer key, ByteBuffer element, @Nullable Integer count, @Nullable Integer rank) {
super(key);
this.element = element;
this.count = count;
this.rank = rank;
}
/**
* Creates a new {@link LPosCommand} for given {@literal element}.
*
* @param element
* @return a new {@link LPosCommand} for {@literal element}.
*/
public static LPosCommand lPosOf(ByteBuffer element) {
return new LPosCommand(null, element, null, null);
}
/**
* Applies the {@literal key}. Constructs a new command instance with all previously configured properties.
*
* @param key must not be {@literal null}.
* @return a new {@link LPosCommand} with {@literal key} applied.
*/
public LPosCommand from(ByteBuffer key) {
Assert.notNull(key, "Key must not be null!");
return new LPosCommand(key, element, count, rank);
}
/**
* Applies the {@literal count} parameter specifying the number of matches to return. Constructs a new command
* instance with all previously configured properties.
*
* @param count can be {@literal null}.
* @return a new {@link LPosCommand} with {@literal count} applied.
*/
public LPosCommand count(Integer count) {
return new LPosCommand(getKey(), element, count, rank);
}
/**
* Applies the {@literal rank} parameter specifying the "rank" of the first element to return. Constructs a new
* command instance with all previously configured properties.
*
* @param rank can be {@literal null}.
* @return a new {@link LPosCommand} with {@literal count} applied.
*/
public LPosCommand rank(Integer rank) {
return new LPosCommand(getKey(), element, count, rank);
}
@Nullable
public Integer getCount() {
return count;
}
@Nullable
public Integer getRank() {
return rank;
}
@Nullable
public ByteBuffer getElement() {
return element;
}
}
/**
* Get first index of the {@literal element} from list at {@literal key}.
*
* @param key must not be {@literal null}.
* @param element
* @return a {@link Mono} emitting the elements index.
* @since 2.4
* @see <a href="https://redis.io/commands/lindex">Redis Documentation: LINDEX</a>
*/
default Mono<Long> lPos(ByteBuffer key, ByteBuffer element) {
Assert.notNull(key, "Key must not be null!");
return lPos(LPosCommand.lPosOf(element).from(key)).next();
}
/**
* Get indices of the {@literal element} from list at {@link LPosCommand#getKey()}.
*
* @param command must not be {@literal null}.
* @return a {@link Flux} emitting the elements indices one by one.
* @since 2.4
* @see <a href="https://redis.io/commands/lindex">Redis Documentation: LINDEX</a>
*/
default Flux<Long> lPos(LPosCommand command) {
return lPos(Mono.just(command)).map(NumericResponse::getOutput);
}
/**
* Get indices of the {@literal element} from list at {@link LPosCommand#getKey()}.
*
* @param commands must not be {@literal null}.
* @return a {@link Flux} emitting the elements indices one by one.
* @since 2.4
* @see <a href="https://redis.io/commands/lindex">Redis Documentation: LINDEX</a>
*/
Flux<NumericResponse<LPosCommand, Long>> lPos(Publisher<LPosCommand> commands);
/**
* {@code LINDEX} command parameters.
*

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* List-specific commands supported by Redis.
@@ -46,6 +47,36 @@ public interface RedisListCommands {
@Nullable
Long rPush(byte[] key, byte[]... values);
/**
* Returns the index of matching elements inside the list stored at given {@literal key}. <br />
* Requires Redis 6.0.6.
*
* @param key must not be {@literal null}.
* @param element must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
* @since 2.4
*/
@Nullable
default Long lPos(byte[] key, byte[] element) {
return CollectionUtils.firstElement(lPos(key, element, null, null));
}
/**
* Returns the index of matching elements inside the list stored at given {@literal key}. <br />
* Requires Redis 6.0.6.
*
* @param key must not be {@literal null}.
* @param element must not be {@literal null}.
* @param rank specifies the "rank" of the first element to return, in case there are multiple matches. A rank of 1
* means to return the first match, 2 to return the second match, and so forth.
* @param count number of matches to return.
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
* @since 2.4
*/
List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count);
/**
* Prepend {@code values} to {@code key}.
*

View File

@@ -49,6 +49,7 @@ import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Convenience extension of {@link RedisConnection} that accepts and returns {@link String}s instead of byte arrays.
@@ -685,6 +686,36 @@ public interface StringRedisConnection extends RedisConnection {
*/
Long rPush(String key, String... values);
/**
* Returns the index of matching elements inside the list stored at given {@literal key}. <br />
* Requires Redis 6.0.6.
*
* @param key must not be {@literal null}.
* @param element must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
* @since 2.4
*/
@Nullable
default Long lPos(String key, String element) {
return CollectionUtils.firstElement(lPos(key, element, null, null));
}
/**
* Returns the index of matching elements inside the list stored at given {@literal key}. <br />
* Requires Redis 6.0.6.
*
* @param key must not be {@literal null}.
* @param element must not be {@literal null}.
* @param rank specifies the "rank" of the first element to return, in case there are multiple matches. A rank of 1
* means to return the first match, 2 to return the second match, and so forth.
* @param count number of matches to return.
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
* @since 2.4
*/
List<Long> lPos(String key, String element, @Nullable Integer rank, @Nullable Integer count);
/**
* Prepend {@code values} to {@code key}.
*

View File

@@ -20,9 +20,11 @@ import java.util.Collections;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisMultiKeyClusterCommandCallback;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -55,6 +57,19 @@ class JedisClusterListCommands implements RedisListCommands {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#l{lPos(byte[], byte[], java.lang.Integer, java.lang.Integer)
*/
@Override
public List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(element, "Element must not be null!");
throw new InvalidDataAccessApiUsageException("LPOS is not supported by jedis.");
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#lPush(byte[], byte[][])

View File

@@ -20,7 +20,9 @@ import redis.clients.jedis.Protocol;
import java.util.ArrayList;
import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -60,6 +62,19 @@ class JedisListCommands implements RedisListCommands {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#l{lPos(byte[], byte[], java.lang.Integer, java.lang.Integer)
*/
@Override
public List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(element, "Element must not be null!");
throw new InvalidDataAccessApiUsageException("LPOS is not supported by jedis.");
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#lPush(byte[], byte[][])

View File

@@ -1136,6 +1136,7 @@ public class LettuceConnection extends AbstractRedisConnection {
COMMAND_OUTPUT_TYPE_MAPPING.put(LINSERT, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LLEN, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSH, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPOS, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LPUSHX, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(LREM, IntegerOutput.class);
COMMAND_OUTPUT_TYPE_MAPPING.put(PTTL, IntegerOutput.class);

View File

@@ -15,13 +15,16 @@
*/
package org.springframework.data.redis.connection.lettuce;
import io.lettuce.core.LPosArgs;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import java.util.Collections;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -61,6 +64,47 @@ class LettuceListCommands implements RedisListCommands {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#l{lPos(byte[], byte[], java.lang.Integer, java.lang.Integer)
*/
@Override
public List<Long> lPos(byte[] key, byte[] element, @Nullable Integer rank, @Nullable Integer count) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(element, "Element must not be null!");
LPosArgs args = new LPosArgs();
if (rank != null) {
args.rank(rank);
}
try {
if (isPipelined()) {
if (count != null) {
pipeline(connection.newLettuceResult(getAsyncConnection().lpos(key, element, count, args)));
} else {
pipeline(connection.newLettuceResult(getAsyncConnection().lpos(key, element, args)));
}
return null;
}
if (isQueueing()) {
if (count != null) {
transaction(connection.newLettuceResult(getAsyncConnection().lpos(key, element, count, args)));
} else {
transaction(connection.newLettuceResult(getAsyncConnection().lpos(key, element, args)));
}
return null;
}
if (count != null) {
return getConnection().lpos(key, element, count, args);
}
return Collections.singletonList(getConnection().lpos(key, element, args));
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisListCommands#lPush(byte[], byte[][])

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection.lettuce;
import io.lettuce.core.LPosArgs;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -146,8 +147,32 @@ class LettuceReactiveListCommands implements ReactiveListCommands {
LettuceConverters.getLowerBoundIndex(range), //
LettuceConverters.getUpperBoundIndex(range));
return result
.map(LettuceConverters::stringToBoolean).map(value -> new BooleanResponse<>(command, value));
return result.map(LettuceConverters::stringToBoolean).map(value -> new BooleanResponse<>(command, value));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveListCommands#lPos(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<LPosCommand, Long>> lPos(Publisher<LPosCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
LPosArgs args = new LPosArgs();
if (command.getRank() != null) {
args.rank(command.getRank());
}
Flux<Long> values;
if (command.getCount() != null) {
values = cmd.lpos(command.getKey(), command.getElement(), command.getCount(), args);
} else {
values = cmd.lpos(command.getKey(), command.getElement(), args).flux();
}
return values.map(value -> new NumericResponse<>(command, value));
}));
}
@@ -238,7 +263,8 @@ class LettuceReactiveListCommands implements ReactiveListCommands {
Assert.notNull(command.getDirection(), "Direction must not be null!");
Mono<ByteBuffer> popResult = ObjectUtils.nullSafeEquals(Direction.RIGHT, command.getDirection())
? cmd.rpop(command.getKey()) : cmd.lpop(command.getKey());
? cmd.rpop(command.getKey())
: cmd.lpop(command.getKey());
return popResult.map(value -> new ByteBufferResponse<>(command, value));
}));

View File

@@ -169,6 +169,28 @@ public interface BoundListOperations<K, V> extends BoundKeyOperations<K> {
@Nullable
V index(long index);
/**
* Returns the index of the first occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param value must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction or when not contained in list.
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Long indexOf(V value);
/**
* Returns the index of the last occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param value must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction or when not contained in list.
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Long lastIndexOf(V value);
/**
* Removes and returns first element in list stored at the bound key.
*

View File

@@ -55,6 +55,24 @@ class DefaultBoundListOperations<K, V> extends DefaultBoundKeyOperations<K> impl
return ops.index(getKey(), index);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundListOperations#indexOf(java.lang.Object)
*/
@Override
public Long indexOf(V value) {
return ops.indexOf(getKey(), value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundListOperations#lastIndexOf(java.lang.Object)
*/
@Override
public Long lastIndexOf(V value) {
return ops.lastIndexOf(getKey(), value);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundListOperations#leftPop()

View File

@@ -53,6 +53,34 @@ class DefaultListOperations<K, V> extends AbstractOperations<K, V> implements Li
}, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ListOperations#indexOf(java.lang.Object, java.lang.Object)
*/
@Override
public Long indexOf(K key, V value) {
byte[] rawKey = rawKey(key);
byte[] rawValue = rawValue(value);
return execute(connection -> connection.lPos(rawKey, rawValue), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ListOperations#lastIndexOf(java.lang.Object, java.lang.Object)
*/
@Override
public Long lastIndexOf(K key, V value) {
byte[] rawKey = rawKey(key);
byte[] rawValue = rawValue(value);
return execute(connection -> {
List<Long> indexes = connection.lPos(rawKey, rawValue, -1, null);
return CollectionUtils.firstElement(indexes);
}, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ListOperations#leftPop(java.lang.Object)

View File

@@ -28,6 +28,7 @@ import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveListCommands;
import org.springframework.data.redis.connection.ReactiveListCommands.LPosCommand;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.Assert;
@@ -232,6 +233,30 @@ class DefaultReactiveListOperations<K, V> implements ReactiveListOperations<K, V
return createMono(connection -> connection.lIndex(rawKey(key), index).map(this::readValue));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveListOperations#indexOf(java.lang.Object, java.lang.Object)
*/
@Override
public Mono<Long> indexOf(K key, V value) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.lPos(rawKey(key), rawValue(value)));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveListOperations#lastIndexOf(java.lang.Object, java.lang.Object)
*/
@Override
public Mono<Long> lastIndexOf(K key, V value) {
Assert.notNull(key, "Key must not be null!");
return createMono(connection -> connection.lPos(LPosCommand.lPosOf(rawValue(value)).from(rawKey(key)).rank(-1)));
}
/* (non-Javadoc)
* @see org.springframework.data.redis.core.ReactiveListOperations#leftPop(java.lang.Object)
*/

View File

@@ -213,6 +213,30 @@ public interface ListOperations<K, V> {
@Nullable
V index(K key, long index);
/**
* Returns the index of the first occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction or when not contained in list.
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Long indexOf(K key, V value);
/**
* Returns the index of the last occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction or when not contained in list.
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Long lastIndexOf(K key, V value);
/**
* Removes and returns first element in list stored at {@code key}.
*

View File

@@ -20,7 +20,6 @@ import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
/**
* Redis list specific operations.
@@ -197,6 +196,30 @@ public interface ReactiveListOperations<K, V> {
*/
Mono<V> index(K key, long index);
/**
* Returns the index of the first occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Mono<Long> indexOf(K key, V value);
/**
* Returns the index of the last occurrence of the specified value in the list at at {@code key}. <br />
* Requires Redis 6.0.6
*
* @param key must not be {@literal null}.
* @param value must not be {@literal null}.
* @return
* @since 2.4
* @see <a href="https://redis.io/commands/lpos">Redis Documentation: LPOS</a>
*/
Mono<Long> lastIndexOf(K key, V value);
/**
* Removes and returns first element in list stored at {@code key}.
*

View File

@@ -250,7 +250,9 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
*/
@Override
public int indexOf(Object o) {
throw new UnsupportedOperationException();
Long index = listOps.indexOf((E) o);
return index != null ? index.intValue() : -1;
}
/*
@@ -259,7 +261,9 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
*/
@Override
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
Long index = listOps.lastIndexOf((E) o);
return index != null ? index.intValue() : -1;
}
/*