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;
}
/*

View File

@@ -1479,6 +1479,72 @@ public abstract class AbstractConnectionIntegrationTests {
verifyResults(Arrays.asList(new Object[] { 2l, Arrays.asList(new String[] { "baz", "bar" }) }));
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPos() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c"));
assertThat((Long) getResults().get(1)).isEqualTo(2);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPosRank() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c", 2, null));
assertThat((List<Long>) getResults().get(1)).containsExactly(6L);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPosNegativeRank() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c", -1, null));
assertThat((List<Long>) getResults().get(1)).containsExactly(7L);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPosCount() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c", null, 2));
assertThat((List<Long>) getResults().get(1)).containsExactly(2L, 6L);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPosRankCount() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c", -1, 2));
assertThat((List<Long>) getResults().get(1)).containsExactly(7L, 6L);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
@WithRedisDriver({ RedisDriver.LETTUCE })
public void lPosCountZero() {
actual.add(connection.rPush("mylist", "a", "b", "c", "1", "2", "3", "c", "c"));
actual.add(connection.lPos("mylist", "c", null, 0));
assertThat((List<Long>) getResults().get(1)).containsExactly(2L, 6L, 7L);
}
// Set operations
@Test

View File

@@ -23,19 +23,23 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Arrays;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.ReactiveListCommands.LPosCommand;
import org.springframework.data.redis.connection.ReactiveListCommands.PopResult;
import org.springframework.data.redis.connection.ReactiveListCommands.PushCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.RangeCommand;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* @author Christoph Strobl
@@ -44,6 +48,8 @@ import org.springframework.data.redis.connection.RedisListCommands.Position;
*/
public class LettuceReactiveListCommandTests extends LettuceReactiveCommandsTestsBase {
@Rule public MinimumRedisVersionRule redisVersion = new MinimumRedisVersionRule();
@Test // DATAREDIS-525
public void rPushShouldAppendValuesCorrectly() {
@@ -304,4 +310,88 @@ public class LettuceReactiveListCommandTests extends LettuceReactiveCommandsTest
assertThat(nativeCommands.llen(KEY_2)).isEqualTo(2L);
assertThat(nativeCommands.lindex(KEY_2, 0)).isEqualTo(VALUE_3);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPos() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands().lPos(KEY_1_BBUFFER, ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))) //
.as(StepVerifier::create) //
.expectNext(2L) //
.verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPosRank() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands()
.lPos(LPosCommand.lPosOf(ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))).from(KEY_1_BBUFFER).rank(2)) //
.as(StepVerifier::create) //
.expectNext(6L) //
.verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPosNegativeRank() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands()
.lPos(LPosCommand.lPosOf(ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))).from(KEY_1_BBUFFER).rank(-1)) //
.as(StepVerifier::create) //
.expectNext(7L) //
.verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPosCount() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands()
.lPos(LPosCommand.lPosOf(ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))).from(KEY_1_BBUFFER).count(2)) //
.as(StepVerifier::create) //
.expectNext(2L) //
.expectNext(6L) //
.verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPosRankCount() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands()
.lPos(LPosCommand.lPosOf(ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))).from(KEY_1_BBUFFER)
.from(KEY_1_BBUFFER).rank(-1).count(2)) //
.as(StepVerifier::create) //
.expectNext(7L) //
.expectNext(6L) //
.verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lPosCountZero() {
nativeCommands.rpush(KEY_1, "a", "b", "c", "1", "2", "3", "c", "c");
connection.listCommands()
.lPos(LPosCommand.lPosOf(ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8))).from(KEY_1_BBUFFER).count(0)) //
.as(StepVerifier::create) //
.expectNext(2L) //
.expectNext(6L) //
.expectNext(7L) //
.verifyComplete();
}
}

View File

@@ -27,15 +27,18 @@ import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.RedisTestProfileValueSource;
import org.springframework.data.redis.StringObjectFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* Integration test of {@link DefaultListOperations}
@@ -49,6 +52,8 @@ import org.springframework.data.redis.StringObjectFactory;
@RunWith(Parameterized.class)
public class DefaultListOperationsTests<K, V> {
@Rule public MinimumRedisVersionRule redisVersion = new MinimumRedisVersionRule();
private RedisTemplate<K, V> redisTemplate;
private ObjectFactory<K> keyFactory;
@@ -284,6 +289,8 @@ public class DefaultListOperationsTests<K, V> {
@SuppressWarnings("unchecked")
public void testLeftPushAllCollection() {
assumeTrue(redisTemplate.getConnectionFactory() instanceof LettuceConnectionFactory);
K key = keyFactory.instance();
V v1 = valueFactory.instance();
@@ -311,4 +318,35 @@ public class DefaultListOperationsTests<K, V> {
assertThatIllegalArgumentException()
.isThrownBy(() -> listOps.leftPushAll(keyFactory.instance(), (Collection<V>) null));
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void indexOf() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
assertThat(listOps.rightPush(key, v1)).isEqualTo(Long.valueOf(1));
assertThat(listOps.rightPush(key, v2)).isEqualTo(Long.valueOf(2));
assertThat(listOps.rightPush(key, v1, v3)).isEqualTo(Long.valueOf(3));
assertThat(listOps.indexOf(key, v1)).isEqualTo(0);
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lastIndexOf() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
assertThat(listOps.rightPush(key, v1)).isEqualTo(Long.valueOf(1));
assertThat(listOps.rightPush(key, v2)).isEqualTo(Long.valueOf(2));
assertThat(listOps.rightPush(key, v1)).isEqualTo(Long.valueOf(3));
assertThat(listOps.rightPush(key, v3)).isEqualTo(Long.valueOf(4));
assertThat(listOps.lastIndexOf(key, v1)).isEqualTo(2);
}
}

View File

@@ -24,6 +24,7 @@ import java.util.Collection;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,6 +35,8 @@ import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* Integration tests for {@link DefaultReactiveListOperations}.
@@ -45,6 +48,8 @@ import org.springframework.data.redis.serializer.RedisSerializer;
@SuppressWarnings("unchecked")
public class DefaultReactiveListOperationsIntegrationTests<K, V> {
@Rule public MinimumRedisVersionRule redisVersion = new MinimumRedisVersionRule();
private final ReactiveRedisTemplate<K, V> redisTemplate;
private final ReactiveListOperations<K, V> listOperations;
@@ -366,6 +371,34 @@ public class DefaultReactiveListOperationsIntegrationTests<K, V> {
listOperations.index(key, 1).as(StepVerifier::create).expectNext(value2).verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void indexOf() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
listOperations.rightPushAll(key, v1, v2, v1, v3).as(StepVerifier::create).expectNext(4L).verifyComplete();
listOperations.indexOf(key, v1).as(StepVerifier::create).expectNext(0L).verifyComplete();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lastIndexOf() {
K key = keyFactory.instance();
V v1 = valueFactory.instance();
V v2 = valueFactory.instance();
V v3 = valueFactory.instance();
listOperations.rightPushAll(key, v1, v2, v1, v3).as(StepVerifier::create).expectNext(4L).verifyComplete();
listOperations.lastIndexOf(key, v1).as(StepVerifier::create).expectNext(2L).verifyComplete();
}
@Test // DATAREDIS-602
public void leftPop() {

View File

@@ -24,11 +24,15 @@ import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assumptions;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.test.util.MinimumRedisVersionRule;
import org.springframework.test.annotation.IfProfileValue;
/**
* Integration test for RedisList
@@ -38,6 +42,8 @@ import org.springframework.data.redis.core.RedisTemplate;
*/
public abstract class AbstractRedisListTests<T> extends AbstractRedisCollectionTests<T> {
@Rule public MinimumRedisVersionRule redisVersion = new MinimumRedisVersionRule();
protected RedisList<T> list;
/**
@@ -54,6 +60,7 @@ public abstract class AbstractRedisListTests<T> extends AbstractRedisCollectionT
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
super.setUp();
list = (RedisList<T>) collection;
}
@@ -157,8 +164,12 @@ public abstract class AbstractRedisListTests<T> extends AbstractRedisCollectionT
assertThat(list.addAll(1, asList)).isTrue();
}
@Test(expected = UnsupportedOperationException.class)
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void testIndexOfObject() {
Assumptions.assumeThat(template.getConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
T t1 = getT();
T t2 = getT();
@@ -168,7 +179,7 @@ public abstract class AbstractRedisListTests<T> extends AbstractRedisCollectionT
assertThat(list.indexOf(t2)).isEqualTo(-1);
list.add(t2);
assertThat(list.indexOf(t1)).isEqualTo(1);
assertThat(list.indexOf(t2)).isEqualTo(1);
}
@Test
@@ -519,4 +530,22 @@ public abstract class AbstractRedisListTests<T> extends AbstractRedisCollectionT
public void testTakeLast() {
testPollLast();
}
@Test // DATAREDIS-1196
@IfProfileValue(name = "redisVersion", value = "6.0.6+")
public void lastIndexOf() {
Assumptions.assumeThat(template.getConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
T t1 = getT();
T t2 = getT();
T t3 = getT();
list.add(t1);
list.add(t2);
list.add(t1);
list.add(t3);
assertThat(list.lastIndexOf(t1)).isEqualTo(2);
}
}