DATAREDIS-698 - Add support for HSTRLEN.

We now support HSTRLEN command throug RedisHashCommands for Lettuce and Jedis in both an imperative and reactive manner.

However, Jedis not natively supporting HSTRLEN via its API we’ve come up with some more reflective invocation allowing to execute commands currently not known by Jedis. We also added this behavior to the cluster implementation which as of now also supports RedisClusterConnection#execute.

Original pull request: #283.
This commit is contained in:
Christoph Strobl
2017-10-03 18:30:25 +02:00
committed by Mark Paluch
parent 9a1518cd40
commit f6a17fb268
24 changed files with 641 additions and 74 deletions

View File

@@ -3201,6 +3201,16 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return this.delegate.hScan(key, options);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisSetCommands#hStrLen(byte[], byte[])
*/
@Nullable
@Override
public Long hStrLen(byte[] key, byte[] field) {
return convertAndReturn(delegate.hStrLen(key, field), identityConverter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisServerCommands#setClientName(java.lang.String)
@@ -3263,6 +3273,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#hStrLen(java.lang.String, java.lang.String)
*/
@Override
public Long hStrLen(String key, String field) {
return hStrLen(serialize(key), serialize(field));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.StringRedisConnection#sScan(java.lang.String, org.springframework.data.redis.core.ScanOptions)
@@ -3472,8 +3491,8 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return null;
}
return value == null ? null : ObjectUtils.nullSafeEquals(converter, identityConverter) ? (T) value : (T) converter.convert(value);
return value == null ? null
: ObjectUtils.nullSafeEquals(converter, identityConverter) ? (T) value : (T) converter.convert(value);
}
private void addResultConverter(Converter<?, ?> converter) {

View File

@@ -15,10 +15,14 @@
*/
package org.springframework.data.redis.connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Christoph Strobl
@@ -131,4 +135,23 @@ public interface DefaultedRedisClusterConnection extends RedisClusterConnection,
default List<RedisClientInfo> getClientList(RedisClusterNode node) {
return serverCommands().getClientList(node);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisClusterConnection#execute(String, byte[], Collection)
*/
@Nullable
@Override
default <T> T execute(String command, byte[] key, Collection<byte[]> args) {
Assert.notNull(command, "Command must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(args, "Args must not be null!");
ArrayList<byte[]> allArgs = new ArrayList();
allArgs.add(key);
allArgs.addAll(args);
return (T) execute(command, allArgs.toArray(new byte[allArgs.size()][]));
}
}

View File

@@ -916,6 +916,13 @@ public interface DefaultedRedisConnection extends RedisConnection {
return hashCommands().hScan(key, options);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}. */
@Override
@Deprecated
default Long hStrLen(byte[] key, byte[] field) {
return hashCommands().hStrLen(key, field);
}
// GEO COMMANDS
/** @deprecated in favor of {@link RedisConnection#geoCommands()}}. */

View File

@@ -578,4 +578,83 @@ public interface ReactiveHashCommands {
* @see <a href="http://redis.io/commands/hgetall">Redis Documentation: HGETALL</a>
*/
Flux<CommandResponse<KeyCommand, Flux<Map.Entry<ByteBuffer, ByteBuffer>>>> hGetAll(Publisher<KeyCommand> commands);
/**
* @author Christoph Strobl
* @see <a href="http://redis.io/commands/hstrlen">Redis Documentation: HSTRLEN</a>
* @since 2.1
*/
class HStrLenCommand extends KeyCommand {
private ByteBuffer field;
/**
* Creates a new {@link HStrLenCommand} given a {@code key}.
*
* @param key can be {@literal null}.
* @param field must not be {@literal null}.
*/
private HStrLenCommand(@Nullable ByteBuffer key, ByteBuffer field) {
super(key);
this.field = field;
}
/**
* Specify the {@code field} within the hash to get the length of the {@code value} of.ø
*
* @param field must not be {@literal null}.
* @return new instance of {@link HStrLenCommand}.
*/
public static HStrLenCommand lengthOf(ByteBuffer field) {
Assert.notNull(field, "Field must not be null!");
return new HStrLenCommand(null, field);
}
/**
* Define the {@code key} the hash is stored at.
*
* @param key must not be {@literal null}.
* @return new instance of {@link HStrLenCommand}.
*/
public HStrLenCommand from(ByteBuffer key) {
return new HStrLenCommand(key, field);
}
/**
* @return {@literal null} if not already set.
*/
@Nullable
public ByteBuffer getField() {
return field;
}
}
/**
* Get the length of the value associated with {@code hashKey}. If either the {@code key} or the {@code hashKey} do
* not exist, {@code 0} is emitted.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return never {@literal null}.
* @since 2.1
*/
default Mono<Long> hStrLen(ByteBuffer key, ByteBuffer field) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(field, "Field must not be null!");
return hStrLen(Mono.just(HStrLenCommand.lengthOf(field).from(key))).next().map(NumericResponse::getOutput);
}
/**
* Get the length of the value associated with {@code hashKey}. If either the {@code key} or the {@code hashKey} do
* not exist, {@code 0} is emitted.
*
* @param commands must not be {@literal null}.
* @return never {@literal null}.
* @since 2.1
*/
Flux<NumericResponse<HStrLenCommand, Long>> hStrLen(Publisher<HStrLenCommand> commands);
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection;
import java.util.Collection;
import java.util.Set;
import org.springframework.lang.Nullable;
@@ -56,6 +57,27 @@ public interface RedisClusterConnection extends RedisConnection, RedisClusterCom
@Nullable
byte[] randomKey(RedisClusterNode node);
/**
* Execute the given command for the {@code key} provided potentially appending args. <br />
* This method, other than {@link #execute(String, byte[]...)}, dispatches the command to the {@code key} serving
* master node.
*
* <pre>
* <code>
* // SET foo bar EX 10 NX
* execute("SET", "foo".getBytes(), asBinaryList("bar", "EX", 10, "NX")
* </code>
* </pre>
*
* @param command must not be {@literal null}.
* @param key must not be {@literal null}.
* @param args must not be {@literal null}.
* @return command result as delivered by the underlying Redis driver. Can be {@literal null}.
* @since 2.1
*/
@Nullable
<T> T execute(String command, byte[] key, Collection<byte[]> args);
/**
* Get {@link RedisClusterServerCommands}.
*

View File

@@ -183,4 +183,16 @@ public interface RedisHashCommands {
* @see <a href="http://redis.io/commands/hscan">Redis Documentation: HSCAN</a>
*/
Cursor<Map.Entry<byte[], byte[]>> hScan(byte[] key, ScanOptions options);
/**
* Returns the length of the value associated with {@code field} in the hash stored at {@code key}. If the key or the
* field do not exist, {@code 0} is returned.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
*/
@Nullable
Long hStrLen(byte[] key, byte[] field);
}

View File

@@ -1509,6 +1509,18 @@ public interface StringRedisConnection extends RedisConnection {
*/
Cursor<Map.Entry<String, String>> hScan(String key, ScanOptions options);
/**
* Returns the length of the value associated with {@code field} in the hash stored at {@code key}. If the key or the
* field do not exist, {@code 0} is returned.
*
* @param key must not be {@literal null}.
* @param field must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
*/
@Nullable
Long hStrLen(String key, String field);
// -------------------------------------------------------------------------
// Methods dealing with HyperLogLog
// -------------------------------------------------------------------------

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.connection.jedis;
import redis.clients.jedis.BinaryJedis;
import redis.clients.jedis.Builder;
import redis.clients.jedis.Client;
import redis.clients.jedis.Connection;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.Protocol.Command;
import redis.clients.jedis.Queable;
import redis.clients.jedis.Response;
import redis.clients.util.RedisOutputStream;
import redis.clients.util.SafeEncoder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Christoph Strobl
* @since 2.1
*/
class JedisClientUtils {
private static final Field CLIENT_FIELD;
private static final Method SEND_COMMAND;
private static final Method GET_RESPONSE;
private static final Method PROTOCOL_SEND_COMMAND;
static {
CLIENT_FIELD = ReflectionUtils.findField(BinaryJedis.class, "client", Client.class);
ReflectionUtils.makeAccessible(CLIENT_FIELD);
PROTOCOL_SEND_COMMAND = ReflectionUtils.findMethod(Protocol.class, "sendCommand", RedisOutputStream.class,
byte[].class, byte[][].class);
ReflectionUtils.makeAccessible(PROTOCOL_SEND_COMMAND);
try {
Class<?> commandType = ClassUtils.isPresent("redis.clients.jedis.ProtocolCommand", null)
? ClassUtils.forName("redis.clients.jedis.ProtocolCommand", null)
: ClassUtils.forName("redis.clients.jedis.Protocol$Command", null);
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand",
new Class[] { commandType, byte[][].class });
} catch (Exception e) {
throw new NoClassDefFoundError(
"Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'.");
}
ReflectionUtils.makeAccessible(SEND_COMMAND);
GET_RESPONSE = ReflectionUtils.findMethod(Queable.class, "getResponse", Builder.class);
ReflectionUtils.makeAccessible(GET_RESPONSE);
}
@Nullable
static <T> T execute(String command, Collection<byte[]> keys, Collection<byte[]> args, Supplier<Jedis> jedis) {
List<byte[]> mArgs = new ArrayList<>(keys);
mArgs.addAll(args);
Client client = retrieveClient(jedis.get());
sendCommand(client, command, mArgs.toArray(new byte[mArgs.size()][]));
return (T) client.getOne();
}
static Client retrieveClient(Jedis jedis) {
return (Client) ReflectionUtils.getField(CLIENT_FIELD, jedis);
}
static Client sendCommand(Jedis jedis, String command, byte[][] args) {
Client client = retrieveClient(jedis);
if (isKnownCommand(command)) {
ReflectionUtils.invokeMethod(SEND_COMMAND, client, Command.valueOf(command.trim().toUpperCase()), args);
} else {
sendProtocolCommand(client, command, args);
}
return client;
}
static void sendCommand(Client client, String command, byte[][] args) {
if (isKnownCommand(command)) {
ReflectionUtils.invokeMethod(SEND_COMMAND, client, Command.valueOf(command.trim().toUpperCase()), args);
} else {
sendProtocolCommand(client, command, args);
}
}
static void sendProtocolCommand(Client client, String command, byte[][] args) {
DirectFieldAccessor dfa = new DirectFieldAccessor(client);
client.connect();
RedisOutputStream os = (RedisOutputStream) dfa.getPropertyValue("outputStream");
ReflectionUtils.invokeMethod(PROTOCOL_SEND_COMMAND, null, os, SafeEncoder.encode(command), args);
Integer pipelinedCommands = (Integer) dfa.getPropertyValue("pipelinedCommands");
dfa.setPropertyValue("pipelinedCommands", pipelinedCommands.intValue() + 1);
}
static boolean isKnownCommand(String command) {
try {
Command.valueOf(command);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
static boolean isInMulti(Jedis jedis) {
return retrieveClient(jedis).isInMulti();
}
static Response<Object> getGetResponse(Object target) {
return (Response<Object>) ReflectionUtils.invokeMethod(GET_RESPONSE, target, new Builder<Object>() {
public Object build(Object data) {
return data;
}
public String toString() {
return "Object";
}
});
}
}

View File

@@ -23,7 +23,10 @@ import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisClusterConnectionHandler;
import redis.clients.jedis.JedisPool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -130,8 +133,34 @@ public class JedisClusterConnection implements DefaultedRedisClusterConnection {
@Override
public Object execute(String command, byte[]... args) {
// TODO: execute command on all nodes? or throw exception requiring to execute command on a specific node
throw new UnsupportedOperationException("Execute is currently not supported in cluster mode.");
Assert.notNull(command, "Command must not be null!");
return clusterCommandExecutor
.executeCommandOnArbitraryNode((JedisClusterCommandCallback<Object>) client -> JedisClientUtils.execute(command,
Collections.emptyList(), Arrays.asList(args), () -> client))
.getValue();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisClusterConnection#execute(String, byte[], java.util.Collection)
*/
@Nullable
@Override
public <T> T execute(String command, byte[] key, Collection<byte[]> args) {
Assert.notNull(command, "Command must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(args, "Args must not be null!");
Collection<byte[]> commandArgs = new ArrayList<>();
commandArgs.add(key);
commandArgs.addAll(args);
RedisClusterNode keyMaster = topologyProvider.getTopology().getKeyServingMasterNode(key);
return clusterCommandExecutor.executeCommandOnSingleNode((JedisClusterCommandCallback<T>) client -> JedisClientUtils
.execute(command, Collections.emptyList(), commandArgs, () -> client), keyMaster).getValue();
}
/*
@@ -787,8 +816,7 @@ public class JedisClusterConnection implements DefaultedRedisClusterConnection {
PropertyAccessor accessor = new DirectFieldAccessFallbackBeanWrapper(cluster);
this.connectionHandler = accessor.isReadableProperty("connectionHandler")
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler")
: null;
? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler") : null;
} else {
this.connectionHandler = null;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.data.redis.connection.jedis;
import redis.clients.jedis.ScanParams;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -29,6 +30,7 @@ import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanCursor;
import org.springframework.data.redis.core.ScanIteration;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -286,7 +288,18 @@ class JedisClusterHashCommands implements RedisHashCommands {
}.open();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hStrLen(byte[], byte[])
*/
@Nullable
@Override
public Long hStrLen(byte[] key, byte[] field) {
return Long.class.cast(connection.execute("HSTRLEN", key, Collections.singleton(field)));
}
private DataAccessException convertJedisAccessException(Exception ex) {
return connection.convertJedisAccessException(ex);
}
}

View File

@@ -15,22 +15,15 @@
*/
package org.springframework.data.redis.connection.jedis;
import redis.clients.jedis.BinaryJedis;
import redis.clients.jedis.BinaryJedisPubSub;
import redis.clients.jedis.Builder;
import redis.clients.jedis.Client;
import redis.clients.jedis.Connection;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Protocol.Command;
import redis.clients.jedis.Queable;
import redis.clients.jedis.Response;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.util.Pool;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
@@ -48,10 +41,8 @@ import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.convert.TransactionResultConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -70,38 +61,10 @@ import org.springframework.util.StringUtils;
*/
public class JedisConnection extends AbstractRedisConnection {
private static final Field CLIENT_FIELD;
private static final Method SEND_COMMAND;
private static final Method GET_RESPONSE;
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy(
JedisConverters.exceptionConverter());
static {
CLIENT_FIELD = ReflectionUtils.findField(BinaryJedis.class, "client", Client.class);
ReflectionUtils.makeAccessible(CLIENT_FIELD);
try {
Class<?> commandType = ClassUtils.isPresent("redis.clients.jedis.ProtocolCommand", null)
? ClassUtils.forName("redis.clients.jedis.ProtocolCommand", null)
: ClassUtils.forName("redis.clients.jedis.Protocol$Command", null);
SEND_COMMAND = ReflectionUtils.findMethod(Connection.class, "sendCommand",
new Class[] { commandType, byte[][].class });
} catch (Exception e) {
throw new NoClassDefFoundError(
"Could not find required flavor of command required by 'redis.clients.jedis.Connection#sendCommand'.");
}
ReflectionUtils.makeAccessible(SEND_COMMAND);
GET_RESPONSE = ReflectionUtils.findMethod(Queable.class, "getResponse", Builder.class);
ReflectionUtils.makeAccessible(GET_RESPONSE);
}
private final Jedis jedis;
private final Client client;
private @Nullable Transaction transaction;
private final @Nullable Pool<Jedis> pool;
/**
@@ -117,6 +80,7 @@ public class JedisConnection extends AbstractRedisConnection {
private Queue<FutureResult<Response<?>>> txResults = new LinkedList<>();
class JedisResult extends FutureResult<Response<?>> {
public <T> JedisResult(Response<T> resultHolder, Converter<T, ?> converter) {
super(resultHolder, converter);
}
@@ -180,9 +144,6 @@ public class JedisConnection extends AbstractRedisConnection {
*/
protected JedisConnection(Jedis jedis, @Nullable Pool<Jedis> pool, int dbIndex, String clientName) {
// extract underlying connection for batch operations
client = (Client) ReflectionUtils.getField(CLIENT_FIELD, jedis);
this.jedis = jedis;
this.pool = pool;
this.dbIndex = dbIndex;
@@ -319,21 +280,13 @@ public class JedisConnection extends AbstractRedisConnection {
Collections.addAll(mArgs, args);
}
ReflectionUtils.invokeMethod(SEND_COMMAND, client, Command.valueOf(command.trim().toUpperCase()),
mArgs.toArray(new byte[mArgs.size()][]));
Client client = JedisClientUtils.sendCommand(this.jedis, command, mArgs.toArray(new byte[mArgs.size()][]));
if (isQueueing() || isPipelined()) {
Object target = (isPipelined() ? pipeline : transaction);
@SuppressWarnings("unchecked")
Response<Object> result = (Response<Object>) ReflectionUtils.invokeMethod(GET_RESPONSE, target,
new Builder<Object>() {
public Object build(Object data) {
return data;
}
public String toString() {
return "Object";
}
});
Response<Object> result = JedisClientUtils.getGetResponse(target);
if (isPipelined()) {
pipeline(new JedisResult(result));
} else {
@@ -378,19 +331,6 @@ public class JedisConnection extends AbstractRedisConnection {
}
// else close the connection normally (doing the try/catch dance)
Exception exc = null;
if (isQueueing()) {
try {
client.quit();
} catch (Exception o_O) {
// ignore exception
}
try {
client.disconnect();
} catch (Exception o_O) {
// ignore exception
}
return;
}
try {
jedis.quit();
} catch (Exception ex) {
@@ -433,7 +373,7 @@ public class JedisConnection extends AbstractRedisConnection {
*/
@Override
public boolean isQueueing() {
return client.isInMulti();
return JedisClientUtils.isInMulti(jedis);
}
/*
@@ -652,7 +592,7 @@ public class JedisConnection extends AbstractRedisConnection {
return new JedisStatusResult(response);
}
<T> JedisStatusResult newStatusResult(Response<T> response, Converter<T,?> converter) {
<T> JedisStatusResult newStatusResult(Response<T> response, Converter<T, ?> converter) {
return new JedisStatusResult(response, converter);
}

View File

@@ -31,6 +31,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;
/**
@@ -414,6 +415,20 @@ class JedisHashCommands implements RedisHashCommands {
}.open();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hStrLen(byte[], byte[])
*/
@Nullable
@Override
public Long hStrLen(byte[] key, byte[] field) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(field, "Field must not be null!");
return Long.class.cast(connection.execute("HSTRLEN", key, field));
}
private boolean isPipelined() {
return connection.isPipelined();
}

View File

@@ -35,6 +35,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;
/**
@@ -418,6 +419,31 @@ class LettuceHashCommands implements RedisHashCommands {
}.open();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisHashCommands#hStrLen(byte[], byte[])
*/
@Nullable
@Override
public Long hStrLen(byte[] key, byte[] field) {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(field, "Field must not be null!");
try {
if (isPipelined()) {
pipeline(connection.newLettuceResult(getAsyncConnection().hstrlen(key, field)));
return null;
}
if (isQueueing()) {
transaction(connection.newLettuceTxResult(getConnection().hstrlen(key, field)));
return null;
}
return getConnection().hstrlen(key, field);
} catch (Exception ex) {
throw convertLettuceAccessException(ex);
}
}
private boolean isPipelined() {
return connection.isPipelined();
}

View File

@@ -214,4 +214,20 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
return Mono.just(new CommandResponse<>(command, result.flatMapMany(v -> Flux.fromStream(v.entrySet().stream()))));
}));
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ReactiveHashCommands#hstrlen(org.reactivestreams.Publisher)
*/
@Override
public Flux<NumericResponse<HStrLenCommand, Long>> hStrLen(Publisher<HStrLenCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).flatMap(command -> {
Assert.notNull(command.getKey(), "Command.getKey() must not be null!");
Assert.notNull(command.getField(), "Command.getField() must not be null!");
return cmd.hstrlen(command.getKey(), command.getField()).map(value -> new NumericResponse<>(command, value));
}));
}
}

View File

@@ -96,6 +96,17 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
@Nullable
Set<HK> keys();
/**
* Returns the length of the value associated with {@code hashKey}. If the {@code hashKey} do not exist, {@code 0} is
* returned.
*
* @param hashKey must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
*/
@Nullable
Long lengthOfValue(HK hashKey);
/**
* Get size of hash at the bound key.
*

View File

@@ -22,6 +22,7 @@ import java.util.Map.Entry;
import java.util.Set;
import org.springframework.data.redis.connection.DataType;
import org.springframework.lang.Nullable;
/**
* Default implementation for {@link HashOperations}.
@@ -118,6 +119,16 @@ class DefaultBoundHashOperations<H, HK, HV> extends DefaultBoundKeyOperations<H>
return ops.keys(getKey());
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#lengthOfValue(java.lang.Object, java.lang.Object)
*/
@Nullable
@Override
public Long lengthOfValue(HK hashKey) {
return ops.lengthOfValue(getKey(), hashKey);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.BoundHashOperations#size()

View File

@@ -24,6 +24,7 @@ import java.util.Map.Entry;
import java.util.Set;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.Nullable;
/**
* Default implementation of {@link HashOperations}.
@@ -114,6 +115,19 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
return execute(connection -> connection.hLen(rawKey), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#lengthOfValue(java.lang.Object, java.lang.Object)
*/
@Nullable
@Override
public Long lengthOfValue(K key, HK hashKey) {
byte[] rawKey = rawKey(key);
byte[] rawHashKey = rawHashKey(hashKey);
return execute(connection -> connection.hStrLen(rawKey, rawHashKey), true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.HashOperations#putAll(java.lang.Object, java.util.Map)

View File

@@ -96,6 +96,18 @@ public interface HashOperations<H, HK, HV> {
*/
Set<HK> keys(H key);
/**
* Returns the length of the value associated with {@code hashKey}. If either the {@code key} or the {@code hashKey}
* do not exist, {@code 0} is returned.
*
* @param key must not be {@literal null}.
* @param hashKey must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
*/
@Nullable
Long lengthOfValue(H key, HK hashKey);
/**
* Get size of hash at {@code key}.
*