diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 37d1f1049..7016d2cc1 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -15,8 +15,18 @@ */ package org.springframework.data.redis.connection; -import java.util.*; +import java.time.Duration; +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.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; @@ -1079,6 +1089,33 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.sort(key, params), identityConverter); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#encoding(byte[]) + */ + @Override + public ValueEncoding encodingOf(byte[] key) { + return convertAndReturn(delegate.encodingOf(key), identityConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#idletime(byte[]) + */ + @Override + public Duration idletime(byte[] key) { + return convertAndReturn(delegate.idletime(key), identityConverter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#refcount(byte[]) + */ + @Override + public Long refcount(byte[] key) { + return convertAndReturn(delegate.refcount(key), identityConverter); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisSetCommands#sPop(byte[]) @@ -1305,7 +1342,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zIncrBy(key, increment, value), 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[][]) */ @@ -1566,7 +1603,7 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.zScore(key, value), 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[][]) */ @@ -2399,6 +2436,33 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(delegate.sort(serialize(key), params), byteListToStringList); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#encoding(java.lang.String) + */ + @Override + public ValueEncoding encodingOf(String key) { + return encodingOf(serialize(key)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#idletime(java.lang.String) + */ + @Override + public Duration idletime(String key) { + return idletime(serialize(key)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.StringRedisConnection#refcount(java.lang.String) + */ + @Override + public Long refcount(String key) { + return refcount(serialize(key)); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#sPop(java.lang.String) diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index d40766594..b75aab1f2 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -223,6 +224,27 @@ public interface DefaultedRedisConnection extends RedisConnection { return keyCommands().sort(key, params, sortKey); } + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default ValueEncoding encodingOf(byte[] key) { + return keyCommands().encodingOf(key); + } + + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default Duration idletime(byte[] key) { + return keyCommands().idletime(key); + } + + /** @deprecated in favor of {@link RedisConnection#keyCommands()}. */ + @Override + @Deprecated + default Long refcount(byte[] key) { + return keyCommands().refcount(key); + } + // STRING COMMANDS /** @deprecated in favor of {@link RedisConnection#stringCommands()}}. */ diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java index 8e0dd8771..182374eb9 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java @@ -692,4 +692,37 @@ public interface ReactiveKeyCommands { * @see Redis Documentation: MOVE */ Flux> move(Publisher commands); + + /** + * Get the type of internal representation used for storing the value at the given {@code key}. + * + * @param key must not be {@literal null}. + * @return the {@link Mono} emitting {@link org.springframework.data.redis.connection.ValueEncoding}. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT ENCODING + * @since 2.1 + */ + Mono encodingOf(ByteBuffer key); + + /** + * Get the {@link Duration} since the object stored at the given {@code key} is idle. + * + * @param key must not be {@literal null}. + * @return the {@link Mono} emitting the idletime of the key of {@link Mono#empty()} if the key does not exist. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT IDLETIME + * @since 2.1 + */ + Mono idletime(ByteBuffer key); + + /** + * Get the number of references of the value associated with the specified {@code key}. + * + * @param key must not be {@literal null}. + * @return {@link Mono#empty()} if key does not exist. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT REFCOUNT + * @since 2.1 + */ + Mono refcount(ByteBuffer key); } diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java index 27e364178..97ab4d09d 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -303,4 +304,42 @@ public interface RedisKeyCommands { * @see Redis Documentation: RESTORE */ void restore(byte[] key, long ttlInMillis, byte[] serializedValue); + + /** + * Get the type of internal representation used for storing the value at the given {@code key}. + * + * @param key must not be {@literal null}. + * @return {@link org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding#VACANT} if key does not + * exist or {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT ENCODING + * @since 2.1 + */ + @Nullable + ValueEncoding encodingOf(byte[] key); + + /** + * Get the {@link Duration} since the object stored at the given {@code key} is idle. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key does not exist or when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT IDLETIME + * @since 2.1 + */ + @Nullable + Duration idletime(byte[] key); + + /** + * Get the number of references of the value associated with the specified {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key does not exist or when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @see Redis Documentation: OBJECT REFCOUNT + * @since 2.1 + */ + @Nullable + Long refcount(byte[] key); + } diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 505716521..348c57d3e 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -15,6 +15,7 @@ */ package org.springframework.data.redis.connection; +import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.Map; @@ -317,6 +318,39 @@ public interface StringRedisConnection extends RedisConnection { */ Long sort(String key, SortParameters params, String storeKey); + /** + * Get the type of internal representation used for storing the value at the given {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key does not exist or when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @since 2.1 + */ + @Nullable + ValueEncoding encodingOf(String key); + + /** + * Get the {@link Duration} since the object stored at the given {@code key} is idle. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key does not exist or when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @since 2.1 + */ + @Nullable + Duration idletime(String key); + + /** + * Get the number of references of the value associated with the specified {@code key}. + * + * @param key must not be {@literal null}. + * @return {@literal null} if key does not exist or when used in pipeline / transaction. + * @throws IllegalArgumentException if {@code key} is {@literal null}. + * @since 2.1 + */ + @Nullable + Long refcount(String key); + // ------------------------------------------------------------------------- // Methods dealing with values/Redis strings // ------------------------------------------------------------------------- diff --git a/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java b/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java new file mode 100644 index 000000000..ec8d418c9 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/ValueEncoding.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 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; + +import java.util.Optional; + +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +/** + * {@link ValueEncoding} is used for the Redis internal data representation used in order to store the value associated + * with a key.
+ *
+ *
Strings
+ *
{@link RedisValueEncoding#RAW} or {@link RedisValueEncoding#INT}
+ *
Lists
+ *
{@link RedisValueEncoding#ZIPLIST} or {@link RedisValueEncoding#LINKEDLIST}
+ *
Sets
+ *
{@link RedisValueEncoding#INTSET} or {@link RedisValueEncoding#HASHTABLE}
+ *
Hashes
+ *
{@link RedisValueEncoding#ZIPLIST} or {@link RedisValueEncoding#HASHTABLE}
+ *
Sorted Sets
+ *
{@link RedisValueEncoding#ZIPLIST} or {@link RedisValueEncoding#SKIPLIST}
+ *
Absent keys
+ *
{@link RedisValueEncoding#VACANT}
+ *
+ * + * @author Christoph Strobl + * @since 2.1 + */ +public interface ValueEncoding { + + @Nullable + String raw(); + + /** + * Get the {@link ValueEncoding} for given {@code encoding}. + * + * @param encoding can be {@literal null}. + * @return never {@literal null}. + */ + static ValueEncoding of(@Nullable String encoding) { + return RedisValueEncoding.lookup(encoding).orElse(() -> encoding); + } + + /** + * Default {@link ValueEncoding} implementation of encodings used in Redis. + * + * @author Christoph Strobl + * @since 2.1 + */ + enum RedisValueEncoding implements ValueEncoding { + + /** + * Normal string encoding. + */ + RAW("raw"), // + /** + * 64 bit signed interval String representing an integer. + */ + INT("int"), // + /** + * Space saving representation for small lists, hashes and sorted sets. + */ + ZIPLIST("ziplist"), // + /** + * Encoding for large lists. + */ + LINKEDLIST("linkedlist"), // + /** + * Space saving representation for small sets that contain only integers.ΓΈ + */ + INTSET("intset"), // + /** + * Encoding for large hashes. + */ + HASHTABLE("hashtable"), // + /** + * Encoding for sorted sets of any size. + */ + SKIPLIST("skiplist"), // + /** + * No encoding present due to non existing key. + */ + VACANT(null); + + private final @Nullable String raw; + + RedisValueEncoding(@Nullable String raw) { + this.raw = raw; + } + + @Override + public String raw() { + return raw; + } + + @Nullable + static Optional lookup(@Nullable String encoding) { + + for (ValueEncoding valueEncoding : values()) { + if (ObjectUtils.nullSafeEquals(valueEncoding.raw(), encoding)) { + return Optional.of(valueEncoding); + } + } + return Optional.empty(); + } + } +} diff --git a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java index 97261f1b5..25b138f04 100644 --- a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java +++ b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.convert; import lombok.RequiredArgsConstructor; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -424,6 +425,19 @@ abstract public class Converters { return (Converter) MAP_TO_PROPERTIES; } + /** + * Convert the given {@literal nullable seconds} to a {@link Duration} or {@literal null}. + * + * @param seconds can be {@literal null}. + * @return given {@literal seconds} as {@link Duration} or {@literal null}. + * @since 2.1 + */ + @Nullable + public static Duration secondsToDuration(@Nullable Long seconds) { + return seconds != null ? Duration.ofSeconds(seconds) : null; + + } + /** * @author Christoph Strobl * @since 1.8 diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java index 5350a799e..cdf91a499 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import redis.clients.jedis.BinaryJedis; import redis.clients.jedis.ScanParams; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,6 +39,7 @@ import org.springframework.data.redis.connection.RedisClusterNode; import org.springframework.data.redis.connection.RedisKeyCommands; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.SortParameters; +import org.springframework.data.redis.connection.ValueEncoding; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisClusterCommandCallback; import org.springframework.data.redis.connection.jedis.JedisClusterConnection.JedisMultiKeyClusterCommandCallback; @@ -559,6 +561,36 @@ class JedisClusterKeyCommands implements RedisKeyCommands { .resultsAsList().stream().mapToLong(val -> ObjectUtils.nullSafeEquals(val, Boolean.TRUE) ? 1 : 0).sum(); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#encoding(byte[]) + */ + @Nullable + @Override + public ValueEncoding encodingOf(byte[] key) { + throw new UnsupportedOperationException("Jedis does not support OBJECT ENCODING in cluster!"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#idletime(byte[]) + */ + @Nullable + @Override + public Duration idletime(byte[] key) { + throw new UnsupportedOperationException("Jedis does not support OBJECT IDLETIME in cluster!"); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#refcount(byte[]) + */ + @Nullable + @Override + public Long refcount(byte[] key) { + throw new UnsupportedOperationException("Jedis does not support OBJECT REFCOUNT in cluster!"); + } + private DataAccessException convertJedisAccessException(Exception ex) { return connection.convertJedisAccessException(ex); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index 54a847414..ad1bc3618 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -59,6 +59,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.SortParameters; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.SortParameters.Range; +import org.springframework.data.redis.connection.ValueEncoding; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.connection.convert.ListConverter; import org.springframework.data.redis.connection.convert.MapConverter; @@ -188,8 +189,7 @@ abstract public class JedisConverters extends Converters { }; GEO_COORDINATE_TO_POINT_CONVERTER = geoCoordinate -> geoCoordinate != null - ? new Point(geoCoordinate.getLongitude(), geoCoordinate.getLatitude()) - : null; + ? new Point(geoCoordinate.getLongitude(), geoCoordinate.getLatitude()) : null; LIST_GEO_COORDINATE_TO_POINT_CONVERTER = new ListConverter<>(GEO_COORDINATE_TO_POINT_CONVERTER); } @@ -292,10 +292,22 @@ abstract public class JedisConverters extends Converters { return STRING_TO_BYTES.convert(source); } - public static String toString(byte[] source) { + @Nullable + public static String toString(@Nullable byte[] source) { return source == null ? null : SafeEncoder.encode(source); } + /** + * Convert the given {@code source} value to the corresponding {@link ValueEncoding}. + * + * @param source can be {@literal null}. + * @return the {@link ValueEncoding} for given {@code source}. Never {@literal null}. + * @since 2.1 + */ + public static ValueEncoding toEncoding(@Nullable byte[] source) { + return ValueEncoding.of(toString(source)); + } + /** * @param source * @return diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java index 07635eb8e..b4739f1cd 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import redis.clients.jedis.ScanParams; import redis.clients.jedis.SortingParams; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -27,6 +28,8 @@ import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisKeyCommands; import org.springframework.data.redis.connection.SortParameters; +import org.springframework.data.redis.connection.ValueEncoding; +import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanCursor; @@ -701,6 +704,87 @@ class JedisKeyCommands implements RedisKeyCommands { } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#encoding(byte[]) + */ + @Nullable + @Override + public ValueEncoding encodingOf(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newJedisResult(connection.getRequiredPipeline().objectEncoding(key), + JedisConverters::toEncoding, () -> RedisValueEncoding.VACANT)); + return null; + } + if (isQueueing()) { + transaction(connection.newJedisResult(connection.getRequiredTransaction().objectEncoding(key), + JedisConverters::toEncoding, () -> RedisValueEncoding.VACANT)); + return null; + } + return JedisConverters.toEncoding(connection.getJedis().objectEncoding(key)); + } catch (Exception ex) { + throw connection.convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#idletime(byte[]) + */ + @Nullable + @Override + public Duration idletime(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newJedisResult(connection.getRequiredPipeline().objectIdletime(key), + Converters::secondsToDuration)); + return null; + } + if (isQueueing()) { + transaction(connection.newJedisResult(connection.getRequiredTransaction().objectIdletime(key), + Converters::secondsToDuration)); + return null; + } + + return Converters.secondsToDuration(connection.getJedis().objectIdletime(key)); + } catch (Exception ex) { + throw connection.convertJedisAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#refcount(byte[]) + */ + @Nullable + @Override + public Long refcount(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newJedisResult(connection.getRequiredPipeline().objectRefcount(key))); + return null; + } + if (isQueueing()) { + transaction(connection.newJedisResult(connection.getRequiredTransaction().objectRefcount(key))); + return null; + } + + return connection.getJedis().objectRefcount(key); + } catch (Exception ex) { + throw connection.convertJedisAccessException(ex); + } + } + private boolean isPipelined() { return connection.isPipelined(); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java index 7c4b1e79f..837b97f97 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java @@ -24,6 +24,7 @@ import io.lettuce.core.cluster.api.sync.RedisClusterCommands; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -32,6 +33,8 @@ import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisKeyCommands; import org.springframework.data.redis.connection.SortParameters; +import org.springframework.data.redis.connection.ValueEncoding; +import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; import org.springframework.data.redis.connection.convert.Converters; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; @@ -691,6 +694,87 @@ class LettuceKeyCommands implements RedisKeyCommands { } } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#encoding(byte[]) + */ + @Nullable + @Override + public ValueEncoding encodingOf(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newLettuceResult(getAsyncConnection().objectEncoding(key), ValueEncoding::of, + () -> RedisValueEncoding.VACANT)); + return null; + } + if (isQueueing()) { + transaction(connection.newLettuceResult(getAsyncConnection().objectEncoding(key), ValueEncoding::of, + () -> RedisValueEncoding.VACANT)); + return null; + } + + return ValueEncoding.of(getConnection().objectEncoding(key)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#idletime(byte[]) + */ + @Nullable + @Override + public Duration idletime(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newLettuceResult(getAsyncConnection().objectIdletime(key), Converters::secondsToDuration)); + return null; + } + if (isQueueing()) { + transaction( + connection.newLettuceResult(getAsyncConnection().objectIdletime(key), Converters::secondsToDuration)); + return null; + } + + return Converters.secondsToDuration(getConnection().objectIdletime(key)); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisKeyCommands#refcount(byte[]) + */ + @Nullable + @Override + public Long refcount(byte[] key) { + + Assert.notNull(key, "Key must not be null!"); + + try { + if (isPipelined()) { + pipeline(connection.newLettuceResult(getAsyncConnection().objectRefcount(key))); + return null; + } + if (isQueueing()) { + transaction(connection.newLettuceResult(getAsyncConnection().objectRefcount(key))); + return null; + } + + return getConnection().objectRefcount(key); + } catch (Exception ex) { + throw convertLettuceAccessException(ex); + } + } + private boolean isPipelined() { return connection.isPipelined(); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java index c0904da4b..7a454258a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java @@ -20,6 +20,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Collection; import java.util.List; @@ -31,6 +32,8 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.Command import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse; import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; +import org.springframework.data.redis.connection.ValueEncoding; +import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; import org.springframework.util.Assert; /** @@ -97,8 +100,7 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { Assert.notEmpty(keys, "Keys must not be null!"); - return cmd.touch(keys.toArray(new ByteBuffer[keys.size()])) - .map((value) -> new NumericResponse<>(keys, value)); + return cmd.touch(keys.toArray(new ByteBuffer[keys.size()])).map((value) -> new NumericResponse<>(keys, value)); })); } @@ -185,8 +187,7 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { Assert.notEmpty(keys, "Keys must not be null!"); - return cmd.del(keys.toArray(new ByteBuffer[keys.size()])) - .map((value) -> new NumericResponse<>(keys, value)); + return cmd.del(keys.toArray(new ByteBuffer[keys.size()])).map((value) -> new NumericResponse<>(keys, value)); })); } @@ -326,7 +327,8 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { })); } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.redis.connection.ReactiveKeyCommands#move(org.reactivestreams.Publisher) */ @Override @@ -340,4 +342,34 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands { return cmd.move(command.getKey(), command.getDatabase()).map(value -> new BooleanResponse<>(command, value)); })); } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveKeyCommands#encodingOf(java.nio.ByteBuffer) + */ + @Override + public Mono encodingOf(ByteBuffer key) { + + return connection + .execute(cmd -> cmd.objectEncoding(key).map(ValueEncoding::of).defaultIfEmpty(RedisValueEncoding.VACANT)) + .next(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveKeyCommands#idletime(java.nio.ByteBuffer) + */ + @Override + public Mono idletime(ByteBuffer key) { + return connection.execute(cmd -> cmd.objectIdletime(key).map(Duration::ofSeconds)).next(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.ReactiveKeyCommands#refcount(java.nio.ByteBuffer) + */ + @Override + public Mono refcount(ByteBuffer key) { + return connection.execute(cmd -> cmd.objectRefcount(key)).next(); + } } diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index df4d7f11f..f5e0ee5a6 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -27,6 +27,7 @@ import static org.springframework.data.redis.connection.RedisGeoCommands.Distanc import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*; import static org.springframework.data.redis.core.ScanOptions.*; +import java.time.Duration; import java.util.*; import java.util.concurrent.BlockingDeque; import java.util.concurrent.CountDownLatch; @@ -64,6 +65,7 @@ import org.springframework.data.redis.connection.RedisZSetCommands.Range; import org.springframework.data.redis.connection.RedisZSetCommands.Tuple; import org.springframework.data.redis.connection.SortParameters.Order; import org.springframework.data.redis.connection.StringRedisConnection.StringTuple; +import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; @@ -2804,8 +2806,7 @@ public abstract class AbstractConnectionIntegrationTests { actual.add(connection.hStrLen("hash-no-exist", "key-2")); - verifyResults( - Arrays.asList(new Object[] { 0L })); + verifyResults(Arrays.asList(new Object[] { 0L })); } @Test // DATAREDIS-694 @@ -2846,6 +2847,61 @@ public abstract class AbstractConnectionIntegrationTests { verifyResults(Arrays.asList(new Object[] { true, 16L })); } + @Test // DATAREDIS-716 + public void encodingReturnsCorrectly() { + + actual.add(connection.set("encode.this", "1000")); + + actual.add(connection.encodingOf("encode.this")); + + verifyResults(Arrays.asList(new Object[] { true, RedisValueEncoding.INT })); + } + + @Test // DATAREDIS-716 + public void encodingReturnsVacantWhenKeyDoesNotExist() { + + actual.add(connection.encodingOf("encode.this")); + + verifyResults(Arrays.asList(new Object[] { RedisValueEncoding.VACANT })); + } + + @Test // DATAREDIS-716 + public void idletimeReturnsCorrectly() { + + actual.add(connection.set("idle.this", "1000")); + actual.add(connection.get("idle.this")); + + actual.add(connection.idletime("idle.this")); + + verifyResults(Arrays.asList(new Object[] { true, "1000", Duration.ofSeconds(0) })); + } + + @Test // DATAREDIS-716 + public void idldetimeReturnsNullWhenKeyDoesNotExist() { + + actual.add(connection.idletime("idle.this")); + + verifyResults(Arrays.asList(new Object[] { null })); + } + + @Test // DATAREDIS-716 + public void refcountReturnsCorrectly() { + + actual.add(connection.lPush("refcount.this", "1000")); + + actual.add(connection.refcount("refcount.this")); + + verifyResults(Arrays.asList(new Object[] { 1L, 1L })); + } + + @Test // DATAREDIS-716 + public void refcountReturnsNullWhenKeyDoesNotExist() { + + actual.add(connection.refcount("refcount.this")); + + verifyResults(Arrays.asList(new Object[] { null })); + } + protected void verifyResults(List expected) { assertEquals(expected, getResults()); } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java index 79db8f16d..1ca41c6d0 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsTests.java @@ -37,6 +37,7 @@ import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand; import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse; +import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding; import org.springframework.data.redis.test.util.MinimumRedisVersionRule; import org.springframework.test.annotation.IfProfileValue; @@ -189,9 +190,7 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest Flux> input = Flux.just(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Collections.singletonList(KEY_1_BBUFFER)); - Flux result = connection.keyCommands() - .mDel(input) - .map(NumericResponse::getOutput); + Flux result = connection.keyCommands().mDel(input).map(NumericResponse::getOutput); StepVerifier.create(result).expectNextCount(2).verifyComplete(); } @@ -240,9 +239,7 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest Flux> input = Flux.just(Arrays.asList(KEY_1_BBUFFER, KEY_2_BBUFFER), Collections.singletonList(KEY_1_BBUFFER)); - Flux result = connection.keyCommands() - .mUnlink(input) - .map(NumericResponse::getOutput); + Flux result = connection.keyCommands().mUnlink(input).map(NumericResponse::getOutput); StepVerifier.create(result).expectNextCount(2).verifyComplete(); } @@ -374,4 +371,48 @@ public class LettuceReactiveKeyCommandsTests extends LettuceReactiveCommandsTest .expectNext(0L) // .verifyComplete(); } + + @Test // DATAREDIS-716 + public void encodingReturnsCorrectly() { + + nativeCommands.set(KEY_1, "1000"); + + connection.keyCommands().encodingOf(KEY_1_BBUFFER).as(StepVerifier::create).expectNext(RedisValueEncoding.INT) + .verifyComplete(); + } + + @Test // DATAREDIS-716 + public void encodingReturnsVacantWhenKeyDoesNotExist() { + + connection.keyCommands().encodingOf(KEY_1_BBUFFER).as(StepVerifier::create).expectNext(RedisValueEncoding.VACANT) + .verifyComplete(); + } + + @Test // DATAREDIS-716 + public void idletimeReturnsCorrectly() { + + nativeCommands.set(KEY_1, "1000"); + nativeCommands.get(KEY_1); + + connection.keyCommands().idletime(KEY_1_BBUFFER).as(StepVerifier::create).expectNext(Duration.ofSeconds(0)) + .verifyComplete(); + } + + @Test // DATAREDIS-716 + public void idldetimeReturnsNullWhenKeyDoesNotExist() { + connection.keyCommands().idletime(KEY_1_BBUFFER).as(StepVerifier::create).verifyComplete(); + } + + @Test // DATAREDIS-716 + public void refcountReturnsCorrectly() { + + nativeCommands.lpush(KEY_1, "1000"); + + connection.keyCommands().refcount(KEY_1_BBUFFER).as(StepVerifier::create).expectNext(1L).verifyComplete(); + } + + @Test // DATAREDIS-716 + public void refcountReturnsNullWhenKeyDoesNotExist() { + connection.keyCommands().refcount(KEY_1_BBUFFER).as(StepVerifier::create).verifyComplete(); + } }