Add field expiration options to reactive API.
Make sure tests do not run when targeting older server versions. Introduce dedicated objects for time to live and status changes. See #3054
This commit is contained in:
committed by
Mark Paluch
parent
50ea34080c
commit
5228bc7008
@@ -30,6 +30,7 @@ import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.convert.ListConverter;
|
||||
import org.springframework.data.redis.connection.convert.MapConverter;
|
||||
@@ -2571,6 +2572,11 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return convertAndReturn(delegate.hStrLen(key, field), Converters.identityConverter());
|
||||
}
|
||||
|
||||
public @Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, byte[]... fields) {
|
||||
return this.delegate.expireHashField(key, expiration, options, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hExpire(byte[] key, long seconds, byte[]... fields) {
|
||||
return this.delegate.hExpire(key, seconds, fields);
|
||||
@@ -2601,11 +2607,21 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return this.delegate.hTtl(key, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hpTtl(byte[] key, byte[]... fields) {
|
||||
return this.delegate.hpTtl(key, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields) {
|
||||
return this.delegate.hTtl(key, timeUnit, fields);
|
||||
}
|
||||
|
||||
public @Nullable List<Long> expireHashField(String key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, String... fields) {
|
||||
return expireHashField(serialize(key), expiration, options, serializeMulti(fields));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hExpire(String key, long seconds, String... fields) {
|
||||
return hExpire(serialize(key), seconds, serializeMulti(fields));
|
||||
@@ -2641,6 +2657,11 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
|
||||
return hTtl(serialize(key), timeUnit, serializeMulti(fields));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hpTtl(String key, String... fields) {
|
||||
return hTtl(serialize(key), serializeMulti(fields));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientName(byte[] name) {
|
||||
this.delegate.setClientName(name);
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.stream.ByteRecord;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||
@@ -1527,6 +1528,21 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
|
||||
return hashCommands().hTtl(key, timeUnit, fields);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default List<Long> hpTtl(byte[] key, byte[]... fields) {
|
||||
return hashCommands().hpTtl(key, fields);
|
||||
}
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
|
||||
@Override
|
||||
@Deprecated
|
||||
default @Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, byte[]... fields) {
|
||||
return hashCommands().expireHashField(key, expiration, options, fields);
|
||||
}
|
||||
|
||||
// GEO COMMANDS
|
||||
|
||||
/** @deprecated in favor of {@link RedisConnection#geoCommands()}}. */
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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.Objects;
|
||||
|
||||
import org.springframework.lang.Contract;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.5
|
||||
*/
|
||||
public interface Hash {
|
||||
|
||||
class FieldExpirationOptions {
|
||||
|
||||
private static final FieldExpirationOptions NONE = new FieldExpirationOptions(null);
|
||||
private @Nullable Condition condition;
|
||||
|
||||
FieldExpirationOptions(@Nullable Condition condition) {
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public static FieldExpirationOptions none() {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
@Contract("_ -> new")
|
||||
public static FieldExpireOptionsBuilder builder() {
|
||||
return new FieldExpireOptionsBuilder();
|
||||
}
|
||||
|
||||
public @Nullable Condition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FieldExpirationOptions that = (FieldExpirationOptions) o;
|
||||
return ObjectUtils.nullSafeEquals(this.condition, that.condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(condition);
|
||||
}
|
||||
|
||||
public static class FieldExpireOptionsBuilder {
|
||||
|
||||
@Nullable Condition condition;
|
||||
|
||||
@Contract("_ -> this")
|
||||
public FieldExpireOptionsBuilder nx() {
|
||||
this.condition = Condition.NX;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Contract("_ -> this")
|
||||
public FieldExpireOptionsBuilder xx() {
|
||||
this.condition = Condition.XX;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Contract("_ -> this")
|
||||
public FieldExpireOptionsBuilder gt() {
|
||||
this.condition = Condition.GT;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Contract("_ -> this")
|
||||
public FieldExpireOptionsBuilder lt() {
|
||||
this.condition = Condition.LT;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Contract("_ -> !null")
|
||||
public FieldExpirationOptions build() {
|
||||
return condition == null ? NONE : new FieldExpirationOptions(condition);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Condition {
|
||||
|
||||
/** Set expiration only when the field has no expiration. */
|
||||
NX,
|
||||
/** Set expiration only when the field has an existing expiration. */
|
||||
XX,
|
||||
/** Set expiration only when the new expiration is greater than current one. */
|
||||
GT,
|
||||
/** Set expiration only when the new expiration is greater than current one. */
|
||||
LT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,12 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.Command;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyScan
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiValueResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -846,54 +849,59 @@ public interface ReactiveHashCommands {
|
||||
Flux<NumericResponse<HStrLenCommand, Long>> hStrLen(Publisher<HStrLenCommand> commands);
|
||||
|
||||
/**
|
||||
* @author Tihomir Mateev
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
class Expire extends HashFieldsCommand {
|
||||
class ExpireCommand extends HashFieldsCommand {
|
||||
|
||||
private final Duration ttl;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Expire} given a {@code key}, a {@link List} of {@code fields} and a time-to-live
|
||||
*
|
||||
* @param key can be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param ttl the duration of the time to live.
|
||||
*/
|
||||
private Expire(@Nullable ByteBuffer key, List<ByteBuffer> fields, Duration ttl) {
|
||||
private final Expiration expiration;
|
||||
private final FieldExpirationOptions options;
|
||||
|
||||
private ExpireCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields, Expiration expiration,
|
||||
FieldExpirationOptions options) {
|
||||
super(key, fields);
|
||||
this.ttl = ttl;
|
||||
this.expiration = expiration;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@code fields} within the hash to set an expiration for.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return new instance of {@link Expire}.
|
||||
*/
|
||||
public static Expire expire(List<ByteBuffer> fields, Duration ttl) {
|
||||
public static ExpireCommand expire(List<ByteBuffer> fields, long timeout, TimeUnit unit) {
|
||||
|
||||
Assert.notNull(fields, "Field must not be null");
|
||||
return new Expire(null, fields, ttl);
|
||||
return expire(fields, Expiration.from(timeout, unit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@code key} the hash is stored at.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return new instance of {@link Expire}.
|
||||
*/
|
||||
public Expire from(ByteBuffer key) {
|
||||
return new Expire(key, getFields(), ttl);
|
||||
public static ExpireCommand expire(List<ByteBuffer> fields, Duration ttl) {
|
||||
|
||||
Assert.notNull(fields, "Field must not be null");
|
||||
return expire(fields, Expiration.from(ttl));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ttl.
|
||||
*/
|
||||
public Duration getTtl() {
|
||||
return ttl;
|
||||
public static ExpireCommand expire(List<ByteBuffer> fields, Expiration expiration) {
|
||||
return new ExpireCommand(null, fields, expiration, FieldExpirationOptions.none());
|
||||
}
|
||||
|
||||
public static ExpireCommand expireAt(List<ByteBuffer> fields, Instant ttl, TimeUnit precision) {
|
||||
|
||||
if (precision.compareTo(TimeUnit.MILLISECONDS) > 0) {
|
||||
return expire(fields, Expiration.unixTimestamp(ttl.getEpochSecond(), TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
return expire(fields, Expiration.unixTimestamp(ttl.toEpochMilli(), TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
public ExpireCommand from(ByteBuffer key) {
|
||||
return new ExpireCommand(key, getFields(), expiration, options);
|
||||
}
|
||||
|
||||
public ExpireCommand withOptions(FieldExpirationOptions options) {
|
||||
return new ExpireCommand(getKey(), getFields(), getExpiration(), options);
|
||||
}
|
||||
|
||||
public Expiration getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public FieldExpirationOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,51 +911,53 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @param duration must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
|
||||
* due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Mono<Long> hExpire(ByteBuffer key, Duration duration, ByteBuffer field) {
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
return hExpire(key, duration, Collections.singletonList(field)).singleOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
|
||||
* passed.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param duration must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
|
||||
* is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
|
||||
* is not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Flux<Long> hExpire(ByteBuffer key, Duration duration, List<ByteBuffer> fields) {
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
|
||||
return hExpire(Flux.just(Expire.expire(fields, duration).from(key)))
|
||||
return expireHashField(Flux.just(ExpireCommand.expire(fields, duration).from(key)))
|
||||
.mapNotNull(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
|
||||
* passed.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
|
||||
* is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
|
||||
* is not met); {@code -2} indicating there is no such field;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
*/
|
||||
Flux<NumericResponse<Expire, Long>> hExpire(Publisher<Expire> commands);
|
||||
Flux<NumericResponse<ExpireCommand, Long>> expireHashField(Publisher<ExpireCommand> commands);
|
||||
|
||||
/**
|
||||
* Expire a given {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
|
||||
@@ -955,104 +965,41 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @param duration must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
|
||||
* due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Mono<Long> hpExpire(ByteBuffer key, Duration duration, ByteBuffer field) {
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
return hpExpire(key, duration, Collections.singletonList(field)).singleOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has
|
||||
* passed.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param duration must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time
|
||||
* is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition
|
||||
* is not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Flux<Long> hpExpire(ByteBuffer key, Duration duration, List<ByteBuffer> fields) {
|
||||
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
|
||||
return hpExpire(Flux.just(Expire.expire(fields, duration).from(key)))
|
||||
return expireHashField(Flux.just(new ExpireCommand(key, fields,
|
||||
Expiration.from(duration.toMillis(), TimeUnit.MILLISECONDS), FieldExpirationOptions.none())))
|
||||
.mapNotNull(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
|
||||
*/
|
||||
Flux<NumericResponse<Expire, Long>> hpExpire(Publisher<Expire> commands);
|
||||
|
||||
/**
|
||||
* @author Tihomir Mateev
|
||||
* @see <a href="https://redis.io/commands/hexpireat">Redis Documentation: HEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
class ExpireAt extends HashFieldsCommand {
|
||||
|
||||
private final Instant expireAt;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExpireAt} given a {@code key}, a {@link List} of {@literal fields} and a {@link Instant}
|
||||
*
|
||||
* @param key can be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param expireAt the {@link Instant} to expire at.
|
||||
*/
|
||||
private ExpireAt(@Nullable ByteBuffer key, List<ByteBuffer> fields, Instant expireAt) {
|
||||
|
||||
super(key, fields);
|
||||
this.expireAt = expireAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@code fields} within the hash to set an expiration for.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return new instance of {@link ExpireAt}.
|
||||
*/
|
||||
public static ExpireAt expireAt(List<ByteBuffer> fields, Instant expireAt) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
return new ExpireAt(null, fields, expireAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@code key} the hash is stored at.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return new instance of {@link ExpireAt}.
|
||||
*/
|
||||
public ExpireAt from(ByteBuffer key) {
|
||||
return new ExpireAt(key, getFields(), expireAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ttl.
|
||||
*/
|
||||
public Instant getExpireAt() {
|
||||
return expireAt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a given {@literal field} in a given {@link Instant} of time, indicated as an absolute
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix timestamp</a> in seconds since Unix epoch
|
||||
@@ -1060,10 +1007,10 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @param expireAt must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
|
||||
* due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is
|
||||
* not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpireat">Redis Documentation: HEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1080,33 +1027,20 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param expireAt must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
|
||||
* expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
|
||||
* GT | LT condition is not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hexpireat">Redis Documentation: HEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Flux<Long> hExpireAt(ByteBuffer key, Instant expireAt, List<ByteBuffer> fields) {
|
||||
Assert.notNull(expireAt, "Duration must not be null");
|
||||
|
||||
return hExpireAt(Flux.just(ExpireAt.expireAt(fields, expireAt).from(key))).mapNotNull(NumericResponse::getOutput);
|
||||
return expireHashField(Flux.just(ExpireCommand.expireAt(fields, expireAt, TimeUnit.SECONDS).from(key)))
|
||||
.mapNotNull(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} in a given {@link Instant} of time, indicated as an absolute
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix timestamp</a> in seconds since Unix epoch
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hexpireat">Redis Documentation: HEXPIREAT</a>
|
||||
*/
|
||||
Flux<NumericResponse<ExpireAt, Long>> hExpireAt(Publisher<ExpireAt> commands);
|
||||
|
||||
/**
|
||||
* Expire a given {@literal field} in a given {@link Instant} of time, indicated as an absolute
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix timestamp</a> in milliseconds since Unix epoch
|
||||
@@ -1114,10 +1048,10 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @param expireAt must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @return a {@link Mono} emitting the expiration result - {@code 2} indicating the specific field is deleted already
|
||||
* due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is
|
||||
* not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hpexpireat">Redis Documentation: HPEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1134,47 +1068,32 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param expireAt must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
|
||||
* expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
|
||||
* GT | LT condition is not met); {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hpexpireat">Redis Documentation: HPEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Flux<Long> hpExpireAt(ByteBuffer key, Instant expireAt, List<ByteBuffer> fields) {
|
||||
Assert.notNull(expireAt, "Duration must not be null");
|
||||
|
||||
return hpExpireAt(Flux.just(ExpireAt.expireAt(fields, expireAt).from(key))).mapNotNull(NumericResponse::getOutput);
|
||||
return expireHashField(Flux.just(ExpireCommand.expireAt(fields, expireAt, TimeUnit.MILLISECONDS).from(key)))
|
||||
.mapNotNull(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a {@link List} of {@literal field} in a given {@link Instant} of time, indicated as an absolute
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix timestamp</a> in milliseconds since Unix epoch
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the expiration results one by one, {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is in the past; {@code 1} indicating expiration time is
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hpexpireat">Redis Documentation: HPEXPIREAT</a>
|
||||
*/
|
||||
Flux<NumericResponse<ExpireAt, Long>> hpExpireAt(Publisher<ExpireAt> commands);
|
||||
|
||||
/**
|
||||
* Persist a given {@literal field} removing any associated expiration, measured as absolute
|
||||
* <a href="https://en.wikipedia.org/wiki/Unix_time">Unix timestamp</a> in seconds since Unix epoch
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the persist result - {@code 1} indicating expiration time is removed;
|
||||
* {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such field;
|
||||
*
|
||||
* @return a {@link Mono} emitting the persist result - {@code 1} indicating expiration time is removed; {@code -1}
|
||||
* field has no expiration time to be removed; {@code -2} indicating there is no such field;
|
||||
* @see <a href="https://redis.io/commands/hpersist">Redis Documentation: HPERSIST</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Mono<Long> hPersist(ByteBuffer key, ByteBuffer field) {
|
||||
|
||||
return hPersist(key, Collections.singletonList(field)).singleOrEmpty();
|
||||
}
|
||||
|
||||
@@ -1183,14 +1102,13 @@ public interface ReactiveHashCommands {
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is removed;
|
||||
* {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such field;
|
||||
*
|
||||
* @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is
|
||||
* removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
|
||||
* field;
|
||||
* @see <a href="https://redis.io/commands/hpersist">Redis Documentation: HPERSIST</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
default Flux<Long> hPersist(ByteBuffer key, List<ByteBuffer> fields) {
|
||||
|
||||
return hPersist(Flux.just(new HashFieldsCommand(key, fields))).mapNotNull(NumericResponse::getOutput);
|
||||
}
|
||||
|
||||
@@ -1198,9 +1116,9 @@ public interface ReactiveHashCommands {
|
||||
* Persist a given {@link List} of {@literal field} removing any associated expiration.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is removed;
|
||||
* {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such field;
|
||||
* * @since 3.5
|
||||
* @return a {@link Flux} emitting the persisting results one by one - {@code 1} indicating expiration time is
|
||||
* removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
|
||||
* field; * @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hpersist">Redis Documentation: HPERSIST</a>
|
||||
*/
|
||||
Flux<NumericResponse<HashFieldsCommand, Long>> hPersist(Publisher<HashFieldsCommand> commands);
|
||||
@@ -1210,9 +1128,9 @@ public interface ReactiveHashCommands {
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the TTL result - the time to live in seconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @return a {@link Mono} emitting the TTL result - the time to live in seconds; or a negative value to signal an
|
||||
* error. The command returns {@code -1} if the key exists but has no associated expiration time. The command
|
||||
* returns {@code -2} if the key does not exist;
|
||||
* @see <a href="https://redis.io/commands/httl">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1226,9 +1144,9 @@ public interface ReactiveHashCommands {
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the TTL results one by one - the time to live in seconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @return a {@link Flux} emitting the TTL results one by one - the time to live in seconds; or a negative value to
|
||||
* signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @see <a href="https://redis.io/commands/httl">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1241,23 +1159,22 @@ public interface ReactiveHashCommands {
|
||||
* Returns the time-to-live of all the given {@literal field} in the {@link List} in seconds.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the persisting results one by one - the time to live in seconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @return a {@link Flux} emitting the persisting results one by one - the time to live in seconds; or a negative
|
||||
* value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
|
||||
* time. The command returns {@code -2} if the key does not exist;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/httl">Redis Documentation: HTTL</a>
|
||||
*/
|
||||
Flux<NumericResponse<HashFieldsCommand, Long>> hTtl(Publisher<HashFieldsCommand> commands);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the time-to-live of a given {@literal field} in milliseconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param field must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the TTL result - the time to live in milliseconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @return a {@link Mono} emitting the TTL result - the time to live in milliseconds; or a negative value to signal an
|
||||
* error. The command returns {@code -1} if the key exists but has no associated expiration time. The command
|
||||
* returns {@code -2} if the key does not exist;
|
||||
* @see <a href="https://redis.io/commands/hpttl">Redis Documentation: HPTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1272,8 +1189,8 @@ public interface ReactiveHashCommands {
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the TTL results one by one - the time to live in milliseconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @see <a href="https://redis.io/commands/hpttl">Redis Documentation: HPTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@@ -1286,9 +1203,9 @@ public interface ReactiveHashCommands {
|
||||
* Returns the time-to-live of all the given {@literal field} in the {@link List} in milliseconds.
|
||||
*
|
||||
* @param commands must not be {@literal null}.
|
||||
* @return a {@link Flux} emitting the persisting results one by one - the time to live in milliseconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist;
|
||||
* @return a {@link Flux} emitting the persisting results one by one - the time to live in milliseconds; or a negative
|
||||
* value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
|
||||
* time. The command returns {@code -2} if the key does not exist;
|
||||
* @since 3.5
|
||||
* @see <a href="https://redis.io/commands/hpttl">Redis Documentation: HPTTL</a>
|
||||
*/
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -252,38 +254,83 @@ public interface RedisHashCommands {
|
||||
@Nullable
|
||||
Long hStrLen(byte[] key, byte[] field);
|
||||
|
||||
default @Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
byte[]... fields) {
|
||||
return expireHashField(key, expiration, FieldExpirationOptions.none(), fields);
|
||||
}
|
||||
|
||||
|
||||
@Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code fields} in seconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param seconds the amount of time after which the fields will be expired in seconds, must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hExpire(byte[] key, long seconds, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code field} in seconds.
|
||||
* Set time to live for given {@code fields}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param seconds the amount of time after which the key will be expired in seconds, must not be {@literal null}.
|
||||
* @param ttl the amount of time after which the fields will be expired in {@link Duration#toSeconds() seconds} precision, must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hExpire(byte[] key, long seconds, byte[]... fields);
|
||||
default List<Long> hExpire(byte[] key, Duration ttl, byte[]... fields) {
|
||||
return hExpire(key, ttl.toSeconds(), fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code field} in milliseconds.
|
||||
* Set time to live for given {@code fields} in milliseconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param millis the amount of time after which the key will be expired in milliseconds, must not be {@literal null}.
|
||||
* @param millis the amount of time after which the fields will be expired in milliseconds, must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpexpire/">Redis Documentation: HPEXPIRE</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hpExpire(byte[] key, long millis, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code fields} in milliseconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param ttl the amount of time after which the fields will be expired in {@link Duration#toMillis() milliseconds} precision, must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is deleted
|
||||
* already due to expiration, or provided expiry interval is 0; {@code 1} indicating expiration time is set/updated;
|
||||
* {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not met);
|
||||
* {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpexpire/">Redis Documentation: HPEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default List<Long> hpExpire(byte[] key, Duration ttl, byte[]... fields) {
|
||||
return hpExpire(key, ttl.toMillis(), fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
|
||||
*
|
||||
@@ -295,7 +342,7 @@ public interface RedisHashCommands {
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpireat/">Redis Documentation: HEXPIREAT</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hExpireAt(byte[] key, long unixTime, byte[]... fields);
|
||||
@@ -311,7 +358,7 @@ public interface RedisHashCommands {
|
||||
* set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX | GT | LT condition is not
|
||||
* met); {@code -2} indicating there is no such field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpexpireat/">Redis Documentation: HPEXPIREAT</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields);
|
||||
@@ -325,27 +372,27 @@ public interface RedisHashCommands {
|
||||
* {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such field;
|
||||
* {@literal null} when used in pipeline / transaction.{@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpersist/">Redis Documentation: HPERSIST</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hPersist(byte[] key, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code field} in seconds.
|
||||
* Get the time to live for {@code fields} in seconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* to signal an error. The command returns {@code -1} if the field exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the field does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hTtl(byte[] key, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code field} in and convert it to the given {@link TimeUnit}.
|
||||
* Get the time to live for {@code fields} in and convert it to the given {@link TimeUnit}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
@@ -354,8 +401,24 @@ public interface RedisHashCommands {
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
// TODO: this is complete nonsense as it would jeopardize negative values
|
||||
// TODO: this should be a List<Map.Entry<byte, Expiration>>
|
||||
List<Long> hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code fields} in milliseconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hpTtl(byte[] key, byte[]... fields);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.redis.connection;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -191,6 +192,20 @@ public interface RedisKeyCommands {
|
||||
@Nullable
|
||||
Boolean expire(byte[] key, long seconds);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code key} using {@link Duration#toSeconds() seconds} precision.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param duration
|
||||
* @return {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default Boolean expire(byte[] key, Duration duration) {
|
||||
return expire(key, duration.toSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code key} in milliseconds.
|
||||
*
|
||||
@@ -202,6 +217,20 @@ public interface RedisKeyCommands {
|
||||
@Nullable
|
||||
Boolean pExpire(byte[] key, long millis);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code key} using {@link Duration#toMillis() milliseconds} precision.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param duration
|
||||
* @return {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default Boolean pExpire(byte[] key, Duration duration) {
|
||||
return pExpire(key, duration.toMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
|
||||
*
|
||||
@@ -213,6 +242,21 @@ public interface RedisKeyCommands {
|
||||
@Nullable
|
||||
Boolean expireAt(byte[] key, long unixTime);
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#getEpochSecond() seconds}
|
||||
* precision.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param unixTime
|
||||
* @return {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default Boolean expireAt(byte[] key, Instant unixTime) {
|
||||
return expireAt(key, unixTime.getEpochSecond());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
|
||||
*
|
||||
@@ -224,6 +268,21 @@ public interface RedisKeyCommands {
|
||||
@Nullable
|
||||
Boolean pExpireAt(byte[] key, long unixTimeInMillis);
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#toEpochMilli()
|
||||
* milliseconds} precision.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param unixTime
|
||||
* @return {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default Boolean pExpireAt(byte[] key, Instant unixTime) {
|
||||
return pExpireAt(key, unixTime.toEpochMilli());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the expiration from given {@code key}.
|
||||
*
|
||||
|
||||
@@ -2333,6 +2333,7 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
@Nullable
|
||||
Long hStrLen(String key, String field);
|
||||
|
||||
// TODO: why why whay is this such a shitty api that there's missing all the NX, XX, GT Options
|
||||
/**
|
||||
* Set time to live for given {@code field} in seconds.
|
||||
*
|
||||
@@ -2412,7 +2413,7 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
List<Long> hPersist(String key, String... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code field} in seconds.
|
||||
* Get the time to live for {@code fields} in seconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
@@ -2420,13 +2421,13 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hTtl(String key, String... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code field} in and convert it to the given {@link TimeUnit}.
|
||||
* Get the time to live for {@code fields} in and convert it to the given {@link TimeUnit}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
@@ -2435,11 +2436,25 @@ public interface StringRedisConnection extends RedisConnection {
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.4
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hTtl(String key, TimeUnit timeUnit, String... fields);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code fields} in seconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: the time to live in milliseconds; or a negative value
|
||||
* to signal an error. The command returns {@code -1} if the key exists but has no associated expiration time.
|
||||
* The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> hpTtl(String key, String... fields);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with HyperLogLog
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import redis.clients.jedis.args.ExpiryOption;
|
||||
import redis.clients.jedis.params.ScanParams;
|
||||
import redis.clients.jedis.resps.ScanResult;
|
||||
|
||||
@@ -26,13 +27,16 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.RedisHashCommands;
|
||||
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.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Cluster {@link RedisHashCommands} implementation for Jedis.
|
||||
@@ -281,16 +285,54 @@ class JedisClusterHashCommands implements RedisHashCommands {
|
||||
|
||||
ScanParams params = JedisConverters.toScanParams(options);
|
||||
|
||||
ScanResult<Entry<byte[], byte[]>> result = connection.getCluster().hscan(key,
|
||||
JedisConverters.toBytes(cursorId),
|
||||
ScanResult<Entry<byte[], byte[]>> result = connection.getCluster().hscan(key, JedisConverters.toBytes(cursorId),
|
||||
params);
|
||||
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
|
||||
}
|
||||
}.open();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<Long> expireHashField(byte[] key, Expiration expiration, FieldExpirationOptions options,
|
||||
byte[]... fields) {
|
||||
|
||||
if (expiration.isPersistent()) {
|
||||
return hPersist(key, fields);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(FieldExpirationOptions.none(), options)) {
|
||||
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return hpExpireAt(key, expiration.getExpirationTimeInMilliseconds(), fields);
|
||||
}
|
||||
return hpExpire(key, expiration.getExpirationTimeInMilliseconds(), fields);
|
||||
}
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return hExpireAt(key, expiration.getExpirationTimeInSeconds(), fields);
|
||||
}
|
||||
return hExpire(key, expiration.getExpirationTimeInSeconds(), fields);
|
||||
}
|
||||
|
||||
ExpiryOption option = ExpiryOption.valueOf(options.getCondition().name());
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.getCluster().hpexpireAt(key, expiration.getExpirationTimeInMilliseconds(), option, fields);
|
||||
}
|
||||
return connection.getCluster().hpexpire(key, expiration.getExpirationTimeInMilliseconds(), option, fields);
|
||||
}
|
||||
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.getCluster().hexpireAt(key, expiration.getExpirationTimeInSeconds(), option, fields);
|
||||
}
|
||||
return connection.getCluster().hexpire(key, expiration.getExpirationTimeInSeconds(), option, fields);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hExpire(byte[] key, long seconds, byte[]... fields) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
|
||||
@@ -368,8 +410,19 @@ class JedisClusterHashCommands implements RedisHashCommands {
|
||||
|
||||
try {
|
||||
return connection.getCluster().httl(key, fields).stream()
|
||||
.map(it -> it != null ? timeUnit.convert(it, TimeUnit.SECONDS) : null)
|
||||
.toList();
|
||||
.map(it -> it != null ? timeUnit.convert(it, TimeUnit.SECONDS) : null).toList();
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hpTtl(byte[] key, byte[]... fields) {
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(fields, "Fields must not be null");
|
||||
|
||||
try {
|
||||
return connection.getCluster().hpttl(key, fields);
|
||||
} catch (Exception ex) {
|
||||
throw convertJedisAccessException(ex);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.redis.connection.jedis;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.args.ExpiryOption;
|
||||
import redis.clients.jedis.commands.PipelineBinaryCommands;
|
||||
import redis.clients.jedis.params.ScanParams;
|
||||
import redis.clients.jedis.resps.ScanResult;
|
||||
@@ -28,6 +29,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.RedisHashCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
@@ -37,6 +39,7 @@ import org.springframework.data.redis.core.ScanIteration;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link RedisHashCommands} implementation for Jedis.
|
||||
@@ -152,7 +155,8 @@ class JedisHashCommands implements RedisHashCommands {
|
||||
|
||||
List<Entry<byte[], byte[]>> convertedMapEntryList = new ArrayList<>(mapEntryList.size());
|
||||
|
||||
mapEntryList.forEach(entry -> convertedMapEntryList.add(Converters.entryOf(entry.getKey(), entry.getValue())));
|
||||
mapEntryList
|
||||
.forEach(entry -> convertedMapEntryList.add(Converters.entryOf(entry.getKey(), entry.getValue())));
|
||||
|
||||
return convertedMapEntryList;
|
||||
|
||||
@@ -239,8 +243,8 @@ class JedisHashCommands implements RedisHashCommands {
|
||||
|
||||
ScanParams params = JedisConverters.toScanParams(options);
|
||||
|
||||
ScanResult<Entry<byte[], byte[]>> result = connection.getJedis().hscan(key,
|
||||
JedisConverters.toBytes(cursorId), params);
|
||||
ScanResult<Entry<byte[], byte[]>> result = connection.getJedis().hscan(key, JedisConverters.toBytes(cursorId),
|
||||
params);
|
||||
return new ScanIteration<>(CursorId.of(result.getCursor()), result.getResult());
|
||||
}
|
||||
|
||||
@@ -262,6 +266,46 @@ class JedisHashCommands implements RedisHashCommands {
|
||||
return connection.invoke().just(Jedis::hpexpire, PipelineBinaryCommands::hpexpire, key, millis, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, byte[]... fields) {
|
||||
|
||||
if (expiration.isPersistent()) {
|
||||
return hPersist(key, fields);
|
||||
}
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(FieldExpirationOptions.none(), options)) {
|
||||
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return hpExpireAt(key, expiration.getExpirationTimeInMilliseconds(), fields);
|
||||
}
|
||||
return hpExpire(key, expiration.getExpirationTimeInMilliseconds(), fields);
|
||||
}
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return hExpireAt(key, expiration.getExpirationTimeInSeconds(), fields);
|
||||
}
|
||||
return hExpire(key, expiration.getExpirationTimeInSeconds(), fields);
|
||||
}
|
||||
|
||||
ExpiryOption option = ExpiryOption.valueOf(options.getCondition().name());
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.invoke().just(Jedis::hpexpireAt, PipelineBinaryCommands::hpexpireAt, key,
|
||||
expiration.getExpirationTimeInMilliseconds(), option, fields);
|
||||
}
|
||||
return connection.invoke().just(Jedis::hpexpire, PipelineBinaryCommands::hpexpire, key,
|
||||
expiration.getExpirationTimeInMilliseconds(), option, fields);
|
||||
}
|
||||
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.invoke().just(Jedis::hexpireAt, PipelineBinaryCommands::hexpireAt, key,
|
||||
expiration.getExpirationTimeInSeconds(), option, fields);
|
||||
}
|
||||
return connection.invoke().just(Jedis::hexpire, PipelineBinaryCommands::hexpire, key,
|
||||
expiration.getExpirationTimeInSeconds(), option, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hExpireAt(byte[] key, long unixTime, byte[]... fields) {
|
||||
return connection.invoke().just(Jedis::hexpireAt, PipelineBinaryCommands::hexpireAt, key, unixTime, fields);
|
||||
@@ -269,7 +313,8 @@ class JedisHashCommands implements RedisHashCommands {
|
||||
|
||||
@Override
|
||||
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields) {
|
||||
return connection.invoke().just(Jedis::hpexpireAt, PipelineBinaryCommands::hpexpireAt, key, unixTimeInMillis, fields);
|
||||
return connection.invoke().just(Jedis::hpexpireAt, PipelineBinaryCommands::hpexpireAt, key, unixTimeInMillis,
|
||||
fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,6 +333,11 @@ class JedisHashCommands implements RedisHashCommands {
|
||||
.toList(Converters.secondsToTimeUnit(timeUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hpTtl(byte[] key, byte[]... fields) {
|
||||
return connection.invoke().just(Jedis::hpttl, PipelineBinaryCommands::hpttl, key, fields);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Long hStrLen(byte[] key, byte[] field) {
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.ExpireArgs;
|
||||
import io.lettuce.core.KeyValue;
|
||||
import io.lettuce.core.MapScanCursor;
|
||||
import io.lettuce.core.ScanArgs;
|
||||
import io.lettuce.core.api.async.RedisHashAsyncCommands;
|
||||
import io.lettuce.core.protocol.CommandArgs;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -27,6 +29,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.RedisHashCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
@@ -36,6 +39,7 @@ import org.springframework.data.redis.core.ScanIteration;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -210,6 +214,46 @@ class LettuceHashCommands implements RedisHashCommands {
|
||||
return hScan(key, CursorId.initial(), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<Long> expireHashField(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
|
||||
FieldExpirationOptions options, byte[]... fields) {
|
||||
|
||||
if (expiration.isPersistent()) {
|
||||
return hPersist(key, fields);
|
||||
}
|
||||
|
||||
ExpireArgs option = new ExpireArgs() {
|
||||
@Override
|
||||
public <K, V> void build(CommandArgs<K, V> args) {
|
||||
|
||||
if(ObjectUtils.nullSafeEquals(options, FieldExpirationOptions.none())) {
|
||||
return;
|
||||
}
|
||||
|
||||
args.add(options.getCondition().name());
|
||||
}
|
||||
};
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.invoke().fromMany(RedisHashAsyncCommands::hpexpireat, key,
|
||||
expiration.getExpirationTimeInMilliseconds(), option, fields).toList();
|
||||
}
|
||||
return connection.invoke()
|
||||
.fromMany(RedisHashAsyncCommands::hpexpire, key, expiration.getExpirationTimeInMilliseconds(), option, fields)
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (expiration.isUnixTimestamp()) {
|
||||
return connection.invoke()
|
||||
.fromMany(RedisHashAsyncCommands::hexpireat, key, expiration.getExpirationTimeInSeconds(), option, fields)
|
||||
.toList();
|
||||
}
|
||||
return connection.invoke()
|
||||
.fromMany(RedisHashAsyncCommands::hexpire, key, expiration.getExpirationTimeInSeconds(), option, fields)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hExpire(byte[] key, long seconds, byte[]... fields) {
|
||||
return connection.invoke().fromMany(RedisHashAsyncCommands::hexpire, key, seconds, fields).toList();
|
||||
@@ -246,6 +290,11 @@ class LettuceHashCommands implements RedisHashCommands {
|
||||
.toList(Converters.secondsToTimeUnit(timeUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> hpTtl(byte[] key, byte[]... fields) {
|
||||
return connection.invoke().fromMany(RedisHashAsyncCommands::hpttl, key, fields).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @param cursorId
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import io.lettuce.core.ExpireArgs;
|
||||
import io.lettuce.core.KeyValue;
|
||||
import io.lettuce.core.ScanStream;
|
||||
import io.lettuce.core.protocol.CommandArgs;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -25,10 +27,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.ReactiveHashCommands;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
|
||||
@@ -38,6 +41,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -265,49 +269,48 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<NumericResponse<Expire, Long>> hExpire(Publisher<Expire> commands) {
|
||||
public Flux<NumericResponse<ExpireCommand, Long>> expireHashField(Publisher<ExpireCommand> commands) {
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null");
|
||||
Assert.notNull(command.getFields(), "Fields must not be null");
|
||||
|
||||
return cmd.hexpire(command.getKey(), command.getTtl().toSeconds(), command.getFields().toArray(ByteBuffer[]::new))
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}));
|
||||
}
|
||||
ByteBuffer[] fields = command.getFields().toArray(ByteBuffer[]::new);
|
||||
|
||||
@Override
|
||||
public Flux<NumericResponse<Expire, Long>> hpExpire(Publisher<Expire> commands) {
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
if (command.getExpiration().isPersistent()) {
|
||||
return cmd.hpersist(command.getKey(), fields).map(value -> new NumericResponse<>(command, value));
|
||||
}
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null");
|
||||
Assert.notNull(command.getFields(), "Fields must not be null");
|
||||
ExpireArgs args = new ExpireArgs() {
|
||||
|
||||
return cmd.hpexpire(command.getKey(), command.getTtl().toMillis(), command.getFields().toArray(ByteBuffer[]::new))
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}));
|
||||
}
|
||||
@Override
|
||||
public <K, V> void build(CommandArgs<K, V> args) {
|
||||
super.build(args);
|
||||
if (ObjectUtils.nullSafeEquals(command.getOptions(), FieldExpirationOptions.none())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<NumericResponse<ExpireAt, Long>> hExpireAt(Publisher<ExpireAt> commands) {
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
args.add(command.getOptions().getCondition().name());
|
||||
}
|
||||
};
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null");
|
||||
Assert.notNull(command.getFields(), "Fields must not be null");
|
||||
if (command.getExpiration().isUnixTimestamp()) {
|
||||
|
||||
return cmd.hexpireat(command.getKey(), command.getExpireAt().getEpochSecond(), command.getFields().toArray(ByteBuffer[]::new))
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}));
|
||||
}
|
||||
if (command.getExpiration().getTimeUnit().equals(TimeUnit.MILLISECONDS)) {
|
||||
return cmd
|
||||
.hpexpireat(command.getKey(), command.getExpiration().getExpirationTimeInMilliseconds(), args, fields)
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}
|
||||
return cmd.hexpireat(command.getKey(), command.getExpiration().getExpirationTimeInSeconds(), args, fields)
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<NumericResponse<ExpireAt, Long>> hpExpireAt(Publisher<ExpireAt> commands) {
|
||||
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
|
||||
if (command.getExpiration().getTimeUnit().equals(TimeUnit.MILLISECONDS)) {
|
||||
return cmd.hpexpire(command.getKey(), command.getExpiration().getExpirationTimeInMilliseconds(), args, fields)
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}
|
||||
|
||||
Assert.notNull(command.getKey(), "Key must not be null");
|
||||
Assert.notNull(command.getFields(), "Fields must not be null");
|
||||
|
||||
return cmd.hpexpireat(command.getKey(), command.getExpireAt().toEpochMilli(), command.getFields().toArray(ByteBuffer[]::new))
|
||||
return cmd.hexpire(command.getKey(), command.getExpiration().getExpirationTimeInSeconds(), args, fields)
|
||||
.map(value -> new NumericResponse<>(command, value));
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -157,6 +159,12 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
@Nullable
|
||||
Long lengthOfValue(HK hashKey);
|
||||
|
||||
default ExpireChanges<HK> expire(Expiration expiration, Collection<HK> hashKeys) {
|
||||
return expire(expiration, FieldExpirationOptions.none(), hashKeys);
|
||||
}
|
||||
|
||||
ExpireChanges<HK> expire(Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Set time to live for given {@code hashKey} (aka field).
|
||||
*
|
||||
@@ -171,7 +179,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> expire(Duration timeout, Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> expire(Duration timeout, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code hashKey} (aka field) as a {@literal date} timestamp.
|
||||
@@ -187,7 +195,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> expireAt(Instant expireAt, Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> expireAt(Instant expireAt, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Remove the expiration from given {@code hashKey} (aka field).
|
||||
@@ -200,7 +208,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> persist(Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> persist(Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) in seconds.
|
||||
@@ -213,7 +221,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> getExpire(Collection<HK> hashKeys);
|
||||
Expirations<HK> getExpire(Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) and convert it to the given {@link TimeUnit}.
|
||||
@@ -227,7 +235,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> getExpire(TimeUnit timeUnit, Collection<HK> hashKeys);
|
||||
Expirations<HK> getExpire(TimeUnit timeUnit, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Get size of hash at the bound key.
|
||||
|
||||
@@ -27,7 +27,10 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.Expirations.Timeouts;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -215,44 +218,88 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expire(K key, Duration duration, Collection<HK> hashKeys) {
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(hashKeys.toArray());
|
||||
long rawTimeout = duration.toMillis();
|
||||
public ExpireChanges<HK> expire(K key, Duration duration, Collection<HK> hashKeys) {
|
||||
|
||||
return execute(connection -> connection.hpExpire(rawKey, rawTimeout, rawHashKeys));
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
|
||||
boolean splitSecond = TimeoutUtils.hasMillis(duration);
|
||||
|
||||
List<Long> raw = execute(connection -> {
|
||||
if (splitSecond) {
|
||||
return connection.hashCommands().hpExpire(rawKey, duration.toMillis(), rawHashKeys);
|
||||
}
|
||||
return connection.hashCommands().hExpire(rawKey, TimeoutUtils.toSeconds(duration), rawHashKeys);
|
||||
});
|
||||
|
||||
return raw != null ? ExpireChanges.of(orderedKeys, raw) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expireAt(K key, Instant instant, Collection<HK> hashKeys) {
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(hashKeys.toArray());
|
||||
public ExpireChanges<HK> expireAt(K key, Instant instant, Collection<HK> hashKeys) {
|
||||
|
||||
return execute(connection -> connection.hpExpireAt(rawKey, instant.toEpochMilli(), rawHashKeys));
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
|
||||
|
||||
Long millis = instant.toEpochMilli();
|
||||
|
||||
List<Long> raw = execute(connection -> TimeoutUtils.containsSplitSecond(millis)
|
||||
? connection.hashCommands().hpExpireAt(rawKey, millis, rawHashKeys)
|
||||
: connection.hashCommands().hExpireAt(rawKey, instant.getEpochSecond(), rawHashKeys));
|
||||
|
||||
return raw != null ? ExpireChanges.of(orderedKeys, raw) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> persist(K key, Collection<HK> hashKeys) {
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(hashKeys.toArray());
|
||||
public ExpireChanges<HK> expire(K key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys) {
|
||||
|
||||
return execute(connection -> connection.hPersist(rawKey, rawHashKeys));
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
|
||||
List<Long> raw = execute(connection -> connection.hashCommands().expireHashField(rawKey, expiration, options, rawHashKeys));
|
||||
|
||||
return raw != null ? ExpireChanges.of(orderedKeys, raw) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(K key, Collection<HK> hashKeys) {
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(hashKeys.toArray());
|
||||
public ExpireChanges<HK> persist(K key, Collection<HK> hashKeys) {
|
||||
|
||||
return execute(connection -> connection.hTtl(rawKey, rawHashKeys));
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
|
||||
|
||||
List<Long> raw = execute(connection -> connection.hashCommands().hPersist(rawKey, rawHashKeys));
|
||||
|
||||
return raw != null ? ExpireChanges.of(orderedKeys, raw) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(K key, TimeUnit timeUnit, Collection<HK> hashKeys) {
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(hashKeys.toArray());
|
||||
public Expirations<HK> getExpire(K key, TimeUnit timeUnit, Collection<HK> hashKeys) {
|
||||
|
||||
return execute(connection -> connection.hTtl(rawKey, timeUnit, rawHashKeys));
|
||||
if(timeUnit.compareTo(TimeUnit.MILLISECONDS) < 0) {
|
||||
throw new IllegalArgumentException("%s precision is not supported must be >= MILLISECONDS".formatted(timeUnit));
|
||||
}
|
||||
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
|
||||
byte[] rawKey = rawKey(key);
|
||||
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
|
||||
|
||||
List<Long> raw = execute(
|
||||
connection -> TimeUnit.MILLISECONDS.equals(timeUnit) ? connection.hashCommands().hpTtl(rawKey, rawHashKeys)
|
||||
: connection.hashCommands().hTtl(rawKey, timeUnit, rawHashKeys));
|
||||
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Timeouts timeouts = new Timeouts(TimeUnit.MILLISECONDS.equals(timeUnit) ? timeUnit : TimeUnit.SECONDS, raw);
|
||||
return Expirations.of(timeUnit, orderedKeys, timeouts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,20 +15,28 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.ReactiveHashCommands.ExpireCommand;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.redis.connection.ReactiveHashCommands;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.core.Expirations.Timeouts;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -63,8 +71,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
Assert.noNullElements(hashKeys, "Hash keys must not contain null elements");
|
||||
|
||||
return createMono(hashCommands -> Flux.fromArray(hashKeys) //
|
||||
.map(hashKey -> (HK) hashKey)
|
||||
.map(this::rawHashKey) //
|
||||
.map(hashKey -> (HK) hashKey).map(this::rawHashKey) //
|
||||
.collectList() //
|
||||
.flatMap(hks -> hashCommands.hDel(rawKey(key), hks)));
|
||||
}
|
||||
@@ -86,8 +93,8 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(hashKey, "Hash key must not be null");
|
||||
|
||||
return createMono(hashCommands -> hashCommands.hGet(rawKey(key), rawHashKey((HK) hashKey))
|
||||
.map(this::readHashValue));
|
||||
return createMono(
|
||||
hashCommands -> hashCommands.hGet(rawKey(key), rawHashKey((HK) hashKey)).map(this::readHashValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,8 +116,8 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(hashKey, "Hash key must not be null");
|
||||
|
||||
return template.doCreateMono(connection -> connection.numberCommands()
|
||||
.hIncrBy(rawKey(key), rawHashKey(hashKey), delta));
|
||||
return template
|
||||
.doCreateMono(connection -> connection.numberCommands().hIncrBy(rawKey(key), rawHashKey(hashKey), delta));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,8 +126,8 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
Assert.notNull(hashKey, "Hash key must not be null");
|
||||
|
||||
return template.doCreateMono(connection -> connection.numberCommands()
|
||||
.hIncrBy(rawKey(key), rawHashKey(hashKey), delta));
|
||||
return template
|
||||
.doCreateMono(connection -> connection.numberCommands().hIncrBy(rawKey(key), rawHashKey(hashKey), delta));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,8 +144,7 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
|
||||
Assert.notNull(key, "Key must not be null");
|
||||
|
||||
return createMono(hashCommands -> hashCommands.hRandFieldWithValues(rawKey(key)))
|
||||
.map(this::deserializeHashEntry);
|
||||
return createMono(hashCommands -> hashCommands.hRandFieldWithValues(rawKey(key))).map(this::deserializeHashEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,6 +241,78 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
|
||||
.map(this::deserializeHashEntry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExpireChanges<HK>> expire(H key, Duration timeout, Collection<HK> hashKeys) {
|
||||
return expire(key, Expiration.from(timeout), FieldExpirationOptions.none(), hashKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys) {
|
||||
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
List<ByteBuffer> rawHashKeys = orderedKeys.stream().map(this::rawHashKey).toList();
|
||||
|
||||
Mono<List<Long>> raw =createFlux(connection -> {
|
||||
return connection.expireHashField(Mono.just(ExpireCommand.expire(rawHashKeys, expiration).from(rawKey).withOptions(options))).map(NumericResponse::getOutput);
|
||||
}).collectList();
|
||||
|
||||
return raw.map(values -> ExpireChanges.of(orderedKeys, values));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Mono<ExpireChanges<HK>> expireAt(H key, Instant expireAt, Collection<HK> hashKeys) {
|
||||
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
List<ByteBuffer> rawHashKeys = orderedKeys.stream().map(this::rawHashKey).toList();
|
||||
|
||||
Mono<List<Long>> raw = createFlux(connection -> connection.hExpireAt(rawKey, expireAt, rawHashKeys)).collectList();
|
||||
|
||||
return raw.map(values -> ExpireChanges.of(orderedKeys, values));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Mono<ExpireChanges<HK>> persist(H key, Collection<HK> hashKeys) {
|
||||
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
List<ByteBuffer> rawHashKeys = orderedKeys.stream().map(this::rawHashKey).toList();
|
||||
|
||||
Mono<List<Long>> raw = createFlux(connection -> connection.hPersist(rawKey, rawHashKeys)).collectList();
|
||||
|
||||
return raw.map(values -> ExpireChanges.of(orderedKeys, values));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Mono<Expirations<HK>> getExpire(H key, TimeUnit timeUnit, Collection<HK> hashKeys) {
|
||||
|
||||
if (timeUnit.compareTo(TimeUnit.MILLISECONDS) < 0) {
|
||||
throw new IllegalArgumentException("%s precision is not supported must be >= MILLISECONDS".formatted(timeUnit));
|
||||
}
|
||||
|
||||
List<HK> orderedKeys = List.copyOf(hashKeys);
|
||||
ByteBuffer rawKey = rawKey(key);
|
||||
List<ByteBuffer> rawHashKeys = orderedKeys.stream().map(this::rawHashKey).toList();
|
||||
|
||||
Mono<List<Long>> raw = createFlux(connection -> {
|
||||
|
||||
if (TimeUnit.MILLISECONDS.equals(timeUnit)) {
|
||||
return connection.hpTtl(rawKey, rawHashKeys);
|
||||
}
|
||||
return connection.hTtl(rawKey, rawHashKeys);
|
||||
}).collectList();
|
||||
|
||||
return raw.map(values -> {
|
||||
|
||||
Timeouts timeouts = new Timeouts(TimeUnit.MILLISECONDS.equals(timeUnit) ? timeUnit : TimeUnit.SECONDS, values);
|
||||
return Expirations.of(timeUnit, orderedKeys, timeouts);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> delete(H key) {
|
||||
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Value Object linking a number of keys to their {@link Expiration} retaining the order of the original source.
|
||||
* Dedicated higher level methods interpret raw expiration values retrieved from a Redis Client.
|
||||
* <ol>
|
||||
* <li>{@link #persistent()} returns keys that do not have an associated time to live</li>
|
||||
* <li>{@link #missing()} returns keys that do not exist and therefore have no associated time to live</li>
|
||||
* <li>{@link #expirations()} returns the ordered list of {@link Expiration expirations} based on the raw values</li>
|
||||
* <li>{@link #expiring()} returns the expiring keys along with their {@link Duration time to live}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.5
|
||||
*/
|
||||
public class Expirations<K> { // TODO: should we move this to let's say Hash.class or another place
|
||||
|
||||
private final TimeUnit unit;
|
||||
private final Map<K, Expiration> expirations;
|
||||
|
||||
Expirations(TimeUnit unit, Map<K, Expiration> expirations) {
|
||||
this.unit = unit;
|
||||
this.expirations = expirations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory Method to create {@link Expirations} from raw sources provided in a given {@link TimeUnit}.
|
||||
*
|
||||
* @param targetUnit the actual time unit of the raw timeToLive values.
|
||||
* @param keys the keys to associated with the raw values in timeToLive. Defines the actual order of entries within
|
||||
* {@link Expirations}.
|
||||
* @param timeouts the raw Redis time to live values.
|
||||
* @return new instance of {@link Expirations}.
|
||||
* @param <K> the key type used
|
||||
*/
|
||||
public static <K> Expirations<K> of(TimeUnit targetUnit, List<K> keys, Timeouts timeouts) {
|
||||
|
||||
if (keys.size() != timeouts.size()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Keys and Timeouts must be of same size but was %s vs %s".formatted(keys.size(), timeouts.size()));
|
||||
}
|
||||
if (keys.size() == 1) {
|
||||
return new Expirations<>(targetUnit,
|
||||
Map.of(keys.iterator().next(), Expiration.of(timeouts.raw().iterator().next(), timeouts.timeUnit())));
|
||||
}
|
||||
|
||||
Map<K, Expiration> target = CollectionUtils.newLinkedHashMap(keys.size());
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
target.put(keys.get(i), Expiration.of(timeouts.get(i), timeouts.timeUnit()));
|
||||
}
|
||||
return new Expirations<>(targetUnit, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered set of keys that do not have a time to live.
|
||||
*/
|
||||
public Set<K> persistent() {
|
||||
return filterByState(Expiration.PERSISTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered set of keys that do not exists and therefore do not have a time to live.
|
||||
*/
|
||||
public Set<K> missing() {
|
||||
return filterByState(Expiration.MISSING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered set of all {@link Expirations expirations} where the {@link Expiration#value()} is using the
|
||||
* {@link TimeUnit} defined in {@link #precision()}.
|
||||
*/
|
||||
public List<Expiration> expirations() {
|
||||
return expirations.values().stream().map(it -> it.convert(this.unit)).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link TimeUnit} for {@link Expiration expirations} held by this instance.
|
||||
*/
|
||||
public TimeUnit precision() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered {@link List} of {@link java.util.Map.Entry entries} combining keys with their actual time to
|
||||
* live. {@link Expiration#isMissing() Missing} and {@link Expiration#isPersistent() persistent} entries are
|
||||
* skipped.
|
||||
*/
|
||||
public List<Map.Entry<K, Duration>> expiring() {
|
||||
return expirations.entrySet().stream().filter(it -> !it.getValue().isMissing() && !it.getValue().isPersistent())
|
||||
.map(it -> Map.entry(it.getKey(), toDuration(it.getValue()))).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return the {@link Expirations expirations} where the {@link Expiration#value()} is using the {@link TimeUnit}
|
||||
* defined in {@link #precision()} or {@literal null} if no entry could be found.
|
||||
*/
|
||||
@Nullable
|
||||
public Expiration expirationOf(K key) {
|
||||
|
||||
Expiration expiration = expirations.get(key);
|
||||
if (expiration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expiration.convert(this.unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return the time to live value of the requested key if it exists and the expiration is neither
|
||||
* {@link Expiration#isMissing() missing} nor {@link Expiration#isPersistent() persistent}, {@literal null}
|
||||
* otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public Duration ttlOf(K key) {
|
||||
|
||||
Expiration expiration = expirationOf(key);
|
||||
if (expiration == null) {
|
||||
return null;
|
||||
}
|
||||
return toDuration(expiration);
|
||||
}
|
||||
|
||||
private Set<K> filterByState(Expiration filter) {
|
||||
return expirations.entrySet().stream().filter(entry -> entry.getValue().equals(filter)).map(Map.Entry::getKey)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Duration toDuration(Expiration expiration) {
|
||||
|
||||
if (expiration.sourceUnit == null) {
|
||||
return null;
|
||||
}
|
||||
return Duration.of(expiration.raw(), expiration.sourceUnit.toChronoUnit());
|
||||
}
|
||||
|
||||
public record Timeouts(TimeUnit timeUnit, List<Long> raw) {
|
||||
|
||||
Long get(int index) {
|
||||
return raw.get(index);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return raw.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expiration holds time to live {@link #raw()} values as returned by a Redis Client. {@link #value()} serves the
|
||||
* actual timeout in the given temporal context converting the {@link #raw()} value into a target {@link TimeUnit}.
|
||||
* Dedicated methods such as {@link #isPersistent()} allow interpretation of the raw result. {@link #MISSING} and
|
||||
* {@link #PERSISTENT} mark predefined states returned by Redis indicating a time to live value could not be retrieved
|
||||
* due to various reasons.
|
||||
*/
|
||||
public static class Expiration { // TODO: is Expiry a better name for this type?
|
||||
|
||||
private final long raw;
|
||||
@Nullable TimeUnit sourceUnit;
|
||||
@Nullable TimeUnit targetUnit;
|
||||
|
||||
public Expiration(long value) {
|
||||
this(value, null);
|
||||
}
|
||||
|
||||
public Expiration(long value, @Nullable TimeUnit sourceUnit) {
|
||||
this(value, sourceUnit, null);
|
||||
}
|
||||
|
||||
public Expiration(long value, @Nullable TimeUnit sourceUnit, @Nullable TimeUnit targetUnit) {
|
||||
this.raw = value;
|
||||
this.sourceUnit = sourceUnit;
|
||||
this.targetUnit = targetUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw source value as returned by the Redis Client.
|
||||
*
|
||||
* @return the raw data
|
||||
*/
|
||||
public long raw() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link #raw()} value converted into the {@link #convert(TimeUnit) requested} target {@link TimeUnit}.
|
||||
*/
|
||||
public long value() {
|
||||
|
||||
if (sourceUnit == null || targetUnit == null) {
|
||||
return raw;
|
||||
}
|
||||
return targetUnit.convert(raw, sourceUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
* @return the {@link Expiration} instance with new target {@link TimeUnit} set for obtaining the {@link #value()
|
||||
* value}, or the same instance raw value cannot or must not be converted.
|
||||
*/
|
||||
public Expiration convert(TimeUnit timeUnit) {
|
||||
|
||||
if (sourceUnit == null || ObjectUtils.nullSafeEquals(sourceUnit, timeUnit)) {
|
||||
return this;
|
||||
}
|
||||
return new Expiration(raw, sourceUnit, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined {@link Expiration} for a key that does not exists and therefore does not have a time to live.
|
||||
*/
|
||||
public static Expiration MISSING = new Expiration(-2L);
|
||||
|
||||
/**
|
||||
* Predefined {@link Expiration} for a key that exists but does not expire.
|
||||
*/
|
||||
public static Expiration PERSISTENT = new Expiration(-1L);
|
||||
|
||||
/**
|
||||
* @return {@literal true} if key exists but does not expire.
|
||||
*/
|
||||
public boolean isPersistent() {
|
||||
return PERSISTENT.equals(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if key does not exists and therefore does not have a time to live.
|
||||
*/
|
||||
public boolean isMissing() {
|
||||
return MISSING.equals(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating {@link Expiration} instances, returning predefined ones if the value matches a known
|
||||
* reserved state.
|
||||
*
|
||||
* @return the {@link Expiration} for the given raw value.
|
||||
*/
|
||||
static Expiration of(Number value, TimeUnit timeUnit) {
|
||||
return switch (value.intValue()) {
|
||||
case -2 -> MISSING;
|
||||
case -1 -> PERSISTENT;
|
||||
default -> new Expiration(value.longValue(), timeUnit);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof Expiration that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.sourceUnit, that.sourceUnit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(this.targetUnit, that.targetUnit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.raw == that.raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Value Object linking a number of keys to their {@link ExpiryChangeState} retaining the order of the original source.
|
||||
* Dedicated higher level methods interpret raw values retrieved from a Redis Client.
|
||||
* <ol>
|
||||
* <li>{@link #ok()} returns keys for which the time to live has been set</li>
|
||||
* <li>{@link #expired()} returns keys that have been expired</li>
|
||||
* <li>{@link #missed()} returns keys for which the time to live could not be set because they do not exist</li>
|
||||
* <li>{@link #skipped()} returns keys for which the time to live has not been set because a precondition was not
|
||||
* met</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.5
|
||||
*/
|
||||
public class ExpireChanges<K> {
|
||||
|
||||
private final Map<K, ExpiryChangeState> changes;
|
||||
|
||||
ExpireChanges(Map<K, ExpiryChangeState> changes) {
|
||||
this.changes = changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory Method to create {@link ExpireChanges} from raw sources.
|
||||
*
|
||||
* @param keys the keys to associated with the raw values in states. Defines the actual order of entries within
|
||||
* {@link ExpireChanges}.
|
||||
* @param states the raw Redis state change values.
|
||||
* @return new instance of {@link ExpireChanges}.
|
||||
* @param <K> the key type used
|
||||
*/
|
||||
public static <K> ExpireChanges<K> of(List<K> keys, List<Long> states) {
|
||||
|
||||
if (keys.size() == 1) {
|
||||
return new ExpireChanges<>(Map.of(keys.iterator().next(), stateFromValue(states.iterator().next())));
|
||||
}
|
||||
|
||||
Map<K, ExpiryChangeState> target = CollectionUtils.newLinkedHashMap(keys.size());
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
target.put(keys.get(i), stateFromValue(states.get(i)));
|
||||
}
|
||||
return new ExpireChanges<>(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered {@link List} of the status changes.
|
||||
*/
|
||||
public List<ExpiryChangeState> stateChanges() {
|
||||
return List.copyOf(changes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the status change for the given {@literal key}, or {@literal null} if {@link ExpiryChangeState} does not
|
||||
* contain an entry for it.
|
||||
*/
|
||||
public ExpiryChangeState stateOf(K key) {
|
||||
return changes.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if all changes are {@link ExpiryChangeState#OK}.
|
||||
*/
|
||||
public boolean allOk() {
|
||||
return allMach(ExpiryChangeState.OK::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if all changes are either ok {@link ExpiryChangeState#OK} or
|
||||
* {@link ExpiryChangeState#EXPIRED}.
|
||||
*/
|
||||
public boolean allChanged() {
|
||||
return allMach(it -> ExpiryChangeState.OK.equals(it) || ExpiryChangeState.EXPIRED.equals(it));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered list of if all changes are {@link ExpiryChangeState#OK}.
|
||||
*/
|
||||
public Set<K> ok() {
|
||||
return filterByState(ExpiryChangeState.OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered list of if all changes are {@link ExpiryChangeState#EXPIRED}.
|
||||
*/
|
||||
public Set<K> expired() {
|
||||
return filterByState(ExpiryChangeState.EXPIRED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered list of if all changes are {@link ExpiryChangeState#DOES_NOT_EXIST}.
|
||||
*/
|
||||
public Set<K> missed() {
|
||||
return filterByState(ExpiryChangeState.DOES_NOT_EXIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an ordered list of if all changes are {@link ExpiryChangeState#CONDITION_NOT_MET}.
|
||||
*/
|
||||
public Set<K> skipped() {
|
||||
return filterByState(ExpiryChangeState.CONDITION_NOT_MET);
|
||||
}
|
||||
|
||||
public boolean allMach(Predicate<ExpiryChangeState> predicate) {
|
||||
return changes.values().stream().allMatch(predicate);
|
||||
}
|
||||
|
||||
private Set<K> filterByState(ExpiryChangeState filter) {
|
||||
return changes.entrySet().stream().filter(entry -> entry.getValue().equals(filter)).map(Map.Entry::getKey)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
private static ExpiryChangeState stateFromValue(Number value) {
|
||||
return ExpiryChangeState.of(value);
|
||||
}
|
||||
|
||||
public record ExpiryChangeState(long value) {
|
||||
|
||||
public static final ExpiryChangeState DOES_NOT_EXIST = new ExpiryChangeState(-2L);
|
||||
public static final ExpiryChangeState CONDITION_NOT_MET = new ExpiryChangeState(0L);
|
||||
public static final ExpiryChangeState OK = new ExpiryChangeState(1L);
|
||||
public static final ExpiryChangeState EXPIRED = new ExpiryChangeState(2L);
|
||||
|
||||
static ExpiryChangeState of(Number value) {
|
||||
return switch (value.intValue()) {
|
||||
case -2 -> DOES_NOT_EXIST;
|
||||
case 0 -> CONDITION_NOT_MET;
|
||||
case 1 -> OK;
|
||||
case 2 -> EXPIRED;
|
||||
default -> new ExpiryChangeState(value.longValue());
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return OK.equals(this);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return EXPIRED.equals(this);
|
||||
}
|
||||
|
||||
public boolean isMissing() {
|
||||
return DOES_NOT_EXIST.equals(this);
|
||||
}
|
||||
|
||||
public boolean isSkipped() {
|
||||
return CONDITION_NOT_MET.equals(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof ExpiryChangeState that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.value == that.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -240,7 +242,7 @@ public interface HashOperations<H, HK, HV> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> expire(H key, Duration timeout, Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> expire(H key, Duration timeout, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code hashKey} (aka field) as a {@literal date} timestamp.
|
||||
@@ -257,7 +259,9 @@ public interface HashOperations<H, HK, HV> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> expireAt(H key, Instant expireAt, Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> expireAt(H key, Instant expireAt, Collection<HK> hashKeys);
|
||||
|
||||
ExpireChanges<HK> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Remove the expiration from given {@code hashKey} (aka field).
|
||||
@@ -271,7 +275,7 @@ public interface HashOperations<H, HK, HV> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> persist(H key, Collection<HK> hashKeys);
|
||||
ExpireChanges<HK> persist(H key, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) in seconds.
|
||||
@@ -285,7 +289,9 @@ public interface HashOperations<H, HK, HV> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> getExpire(H key, Collection<HK> hashKeys);
|
||||
default Expirations<HK> getExpire(H key, Collection<HK> hashKeys) {
|
||||
return getExpire(key, TimeUnit.SECONDS, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) and convert it to the given {@link TimeUnit}.
|
||||
@@ -300,7 +306,8 @@ public interface HashOperations<H, HK, HV> {
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
List<Long> getExpire(H key, TimeUnit timeUnit, Collection<HK> hashKeys);
|
||||
Expirations<HK> getExpire(H key, TimeUnit timeUnit, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
|
||||
@@ -15,13 +15,20 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Reactive Redis operations for Hash Commands.
|
||||
@@ -230,10 +237,80 @@ public interface ReactiveHashOperations<H, HK, HV> {
|
||||
*/
|
||||
Flux<Map.Entry<HK, HV>> scan(H key, ScanOptions options);
|
||||
|
||||
Mono<ExpireChanges<HK>> expire(H key, Duration timeout, Collection<HK> hashKeys);
|
||||
|
||||
Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Set the expiration for given {@code hashKey} (aka field) as a {@literal date} timestamp.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param expireAt must not be {@literal null}.
|
||||
* @param hashKeys must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 2} indicating the specific field is
|
||||
* deleted already due to expiration, or provided expiry interval is in the past; {@code 1} indicating
|
||||
* expiration time is set/updated; {@code 0} indicating the expiration time is not set (a provided NX | XX |
|
||||
* GT | LT condition is not met); {@code -2} indicating there is no such field; {@literal null} when used in
|
||||
* pipeline / transaction.
|
||||
* @throws IllegalArgumentException if the instant is {@literal null} or too large to represent as a {@code Date}.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpireat/">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
Mono<ExpireChanges<HK>> expireAt(H key, Instant expireAt, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Remove the expiration from given {@code hashKey} (aka field).
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param hashKeys must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: {@code 1} indicating expiration time is
|
||||
* removed; {@code -1} field has no expiration time to be removed; {@code -2} indicating there is no such
|
||||
* field; {@literal null} when used in pipeline / transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpersist/">Redis Documentation: HPERSIST</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
Mono<ExpireChanges<HK>> persist(H key, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) in seconds.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param hashKeys must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative
|
||||
* value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
|
||||
* time. The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/httl/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
default Mono<Expirations<HK>> getExpire(H key, Collection<HK> hashKeys) {
|
||||
return getExpire(key, TimeUnit.SECONDS, hashKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time to live for {@code hashKey} (aka field) and convert it to the given {@link TimeUnit}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @param timeUnit must not be {@literal null}.
|
||||
* @param hashKeys must not be {@literal null}.
|
||||
* @return a list of {@link Long} values for each of the fields provided: the time to live in seconds; or a negative
|
||||
* value to signal an error. The command returns {@code -1} if the key exists but has no associated expiration
|
||||
* time. The command returns {@code -2} if the key does not exist; {@literal null} when used in pipeline /
|
||||
* transaction.
|
||||
* @see <a href="https://redis.io/docs/latest/commands/httl/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
@Nullable
|
||||
Mono<Expirations<HK>> getExpire(H key, TimeUnit timeUnit, Collection<HK> hashKeys);
|
||||
|
||||
/**
|
||||
* Removes the given {@literal key}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
*/
|
||||
Mono<Boolean> delete(H key);
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@ public abstract class TimeoutUtils {
|
||||
* @since 2.1
|
||||
*/
|
||||
public static boolean hasMillis(Duration duration) {
|
||||
return duration.toMillis() % 1000 != 0;
|
||||
return containsSplitSecond(duration.toMillis());
|
||||
}
|
||||
|
||||
public static boolean containsSplitSecond(long millis) {
|
||||
return millis % 1000 != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.core.TimeoutUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -105,8 +106,8 @@ public class Expiration {
|
||||
Assert.notNull(duration, "Duration must not be null");
|
||||
|
||||
return duration.isZero() ? Expiration.persistent()
|
||||
: duration.toMillis() % 1000 == 0 ? new Expiration(duration.getSeconds(), TimeUnit.SECONDS)
|
||||
: new Expiration(duration.toMillis(), TimeUnit.MILLISECONDS);
|
||||
: TimeoutUtils.hasMillis(duration) ? new Expiration(duration.toMillis(), TimeUnit.MILLISECONDS)
|
||||
: new Expiration(duration.getSeconds(), TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,13 +20,14 @@ import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.core.Expirations;
|
||||
import org.springframework.data.redis.core.ExpireChanges;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
@@ -327,27 +328,27 @@ public class DefaultRedisMap<K, V> implements RedisMap<K, V> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expire(Duration timeout, Collection<K> hashKeys) {
|
||||
public ExpireChanges<K> expire(Duration timeout, Collection<K> hashKeys) {
|
||||
return Objects.requireNonNull(hashOps.expire(timeout, hashKeys));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expireAt(Instant expireAt, Collection<K> hashKeys) {
|
||||
public ExpireChanges<K> expireAt(Instant expireAt, Collection<K> hashKeys) {
|
||||
return Objects.requireNonNull(hashOps.expireAt(expireAt, hashKeys));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> persist(Collection<K> hashKeys) {
|
||||
public ExpireChanges<K> persist(Collection<K> hashKeys) {
|
||||
return Objects.requireNonNull(hashOps.persist(hashKeys));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(Collection<K> hashKeys) {
|
||||
public Expirations<K> getExpire(Collection<K> hashKeys) {
|
||||
return Objects.requireNonNull(hashOps.getExpire(hashKeys));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(TimeUnit timeUnit, Collection<K> hashKeys) {
|
||||
public Expirations<K> getExpire(TimeUnit timeUnit, Collection<K> hashKeys) {
|
||||
return Objects.requireNonNull(hashOps.getExpire(timeUnit, hashKeys));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,12 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.core.Expirations;
|
||||
import org.springframework.data.redis.core.ExpireChanges;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -91,7 +92,7 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
List<Long> expire(Duration timeout, Collection<K> hashKeys);
|
||||
ExpireChanges<K> expire(Duration timeout, Collection<K> hashKeys);
|
||||
|
||||
/**
|
||||
* Set the expiration for given hash {@code key} as a {@literal date} timestamp.
|
||||
@@ -106,7 +107,7 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hexpireat/">Redis Documentation: HEXPIRE</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
List<Long> expireAt(Instant expireAt, Collection<K> hashKeys);
|
||||
ExpireChanges<K> expireAt(Instant expireAt, Collection<K> hashKeys);
|
||||
|
||||
/**
|
||||
* Remove the expiration from given hash {@code key}.
|
||||
@@ -118,7 +119,7 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
|
||||
* @see <a href="https://redis.io/docs/latest/commands/hpersist/">Redis Documentation: HPERSIST</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
List<Long> persist(Collection<K> hashKeys);
|
||||
ExpireChanges<K> persist(Collection<K> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for hash {@code key} in seconds.
|
||||
@@ -130,7 +131,7 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
|
||||
* @see <a href="https://redis.io/docs/latest/commands/httl/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
List<Long> getExpire(Collection<K> hashKeys);
|
||||
Expirations<K> getExpire(Collection<K> hashKeys);
|
||||
|
||||
/**
|
||||
* Get the time to live for hash {@code key} and convert it to the given {@link TimeUnit}.
|
||||
@@ -143,5 +144,5 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
|
||||
* @see <a href="https://redis.io/docs/latest/commands/httl/">Redis Documentation: HTTL</a>
|
||||
* @since 3.5
|
||||
*/
|
||||
List<Long> getExpire(TimeUnit timeUnit, Collection<K> hashKeys);
|
||||
Expirations<K> getExpire(TimeUnit timeUnit, Collection<K> hashKeys);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.core.Expirations;
|
||||
import org.springframework.data.redis.core.ExpireChanges;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -300,33 +302,38 @@ public class RedisProperties extends Properties implements RedisMap<Object, Obje
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expire(Duration timeout, Collection<Object> hashKeys) {
|
||||
public ExpireChanges<Object> expire(Duration timeout, Collection<Object> hashKeys) {
|
||||
|
||||
Collection<String> keys = hashKeys.stream().map(key -> (String) key).toList();
|
||||
return Objects.requireNonNull(hashOps.expire(timeout, keys));
|
||||
return (ExpireChanges) hashOps.expire(timeout, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> expireAt(Instant expireAt, Collection<Object> hashKeys) {
|
||||
public ExpireChanges<Object> expireAt(Instant expireAt, Collection<Object> hashKeys) {
|
||||
|
||||
Collection<String> keys = hashKeys.stream().map(key -> (String) key).toList();
|
||||
return Objects.requireNonNull(hashOps.expireAt(expireAt, keys));
|
||||
return (ExpireChanges) hashOps.expireAt(expireAt, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> persist(Collection<Object> hashKeys) {
|
||||
public ExpireChanges<Object> persist(Collection<Object> hashKeys) {
|
||||
|
||||
Collection<String> keys = hashKeys.stream().map(key -> (String) key).toList();
|
||||
return Objects.requireNonNull(hashOps.persist(keys));
|
||||
return (ExpireChanges) hashOps.persist(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(Collection<Object> hashKeys) {
|
||||
public Expirations<Object> getExpire(Collection<Object> hashKeys) {
|
||||
|
||||
Collection<String> keys = hashKeys.stream().map(key -> (String) key).toList();
|
||||
return Objects.requireNonNull(hashOps.getExpire(keys));
|
||||
return (Expirations) hashOps.getExpire(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getExpire(TimeUnit timeUnit, Collection<Object> hashKeys) {
|
||||
public Expirations<Object> getExpire(TimeUnit timeUnit, Collection<Object> hashKeys) {
|
||||
|
||||
Collection<String> keys = hashKeys.stream().map(key -> (String) key).toList();
|
||||
return Objects.requireNonNull(hashOps.getExpire(timeUnit, keys));
|
||||
return (Expirations) hashOps.getExpire(timeUnit, keys);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3436,6 +3436,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsSuccessAndSetsTTL() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hExpire("hash-hexpire", 5L, "key-2"));
|
||||
actual.add(connection.hTtl("hash-hexpire", "key-2"));
|
||||
@@ -3449,6 +3450,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hExpire("hash-hexpire", 5L, "missking-field"));
|
||||
actual.add(connection.hExpire("missing-key", 5L, "key-2"));
|
||||
@@ -3459,6 +3461,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsTwoWhenZeroProvided() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hExpire("hash-hexpire", 0, "key-2"));
|
||||
|
||||
@@ -3468,9 +3471,10 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPEXPIRE")
|
||||
public void hpExpireReturnsSuccessAndSetsTTL() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hpExpire("hash-hexpire", 5000L, "key-2"));
|
||||
actual.add(connection.hTtl("hash-hexpire", TimeUnit.MILLISECONDS,"key-2"));
|
||||
actual.add(connection.hpTtl("hash-hexpire", "key-2"));
|
||||
|
||||
List<Object> results = getResults();
|
||||
assertThat(results.get(0)).isEqualTo(Boolean.TRUE);
|
||||
@@ -3481,6 +3485,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPEXPIRE")
|
||||
public void hpExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hpExpire("hash-hexpire", 5L, "missing-field"));
|
||||
actual.add(connection.hpExpire("missing-key", 5L, "key-2"));
|
||||
@@ -3491,6 +3496,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPEXPIRE")
|
||||
public void hpExpireReturnsTwoWhenZeroProvided() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hpExpire("hash-hexpire", 0, "key-2"));
|
||||
|
||||
@@ -3500,6 +3506,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIREAT")
|
||||
public void hExpireAtReturnsSuccessAndSetsTTL() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
|
||||
@@ -3515,6 +3522,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIREAT")
|
||||
public void hExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
|
||||
@@ -3527,6 +3535,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIREAT")
|
||||
public void hExpireAtReturnsTwoWhenZeroProvided() {
|
||||
|
||||
long fiveSecondsAgo = Instant.now().minusSeconds(5L).getEpochSecond();
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
@@ -3538,6 +3547,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIREAT")
|
||||
public void hpExpireAtReturnsSuccessAndSetsTTL() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
|
||||
@@ -3553,6 +3563,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIREAT")
|
||||
public void hpExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
|
||||
@@ -3565,6 +3576,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPEXPIREAT")
|
||||
public void hpExpireAdReturnsTwoWhenZeroProvided() {
|
||||
|
||||
long fiveSecondsAgo = Instant.now().minusSeconds(5L).getEpochSecond();
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
@@ -3576,6 +3588,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPERSIST")
|
||||
public void hPersistReturnsSuccessAndPersistsField() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hExpire("hash-hexpire", 5L, "key-2"));
|
||||
actual.add(connection.hPersist("hash-hexpire", "key-2"));
|
||||
@@ -3587,6 +3600,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPERSIST")
|
||||
public void hPersistReturnsMinusOneWhenFieldDoesNotHaveExpiration() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hPersist("hash-hexpire", "key-2"));
|
||||
|
||||
@@ -3596,6 +3610,7 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HPERSIST")
|
||||
public void hPersistReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hPersist("hash-hexpire", "missing-field"));
|
||||
actual.add(connection.hPersist("missing-key", "key-2"));
|
||||
@@ -3606,15 +3621,27 @@ public abstract class AbstractConnectionIntegrationTests {
|
||||
@Test
|
||||
@EnabledOnCommand("HTTL")
|
||||
public void hTtlReturnsMinusOneWhenFieldHasNoExpiration() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hTtl("hash-hexpire", "key-2"));
|
||||
|
||||
verifyResults(Arrays.asList(Boolean.TRUE, List.of(-1L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HTTL")
|
||||
public void hTtlReturnsMinusIndependendOfTimeUnitOneWhenFieldHasNoExpiration() {
|
||||
|
||||
actual.add(connection.hSet("hash-hexpire", "key-2", "value-2"));
|
||||
actual.add(connection.hTtl("hash-hexpire", TimeUnit.HOURS, "key-2"));
|
||||
|
||||
verifyResults(Arrays.asList(Boolean.TRUE, List.of(-1L)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HTTL")
|
||||
public void hTtlReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
|
||||
actual.add(connection.hTtl("hash-hexpire", "missing-field"));
|
||||
actual.add(connection.hTtl("missing-key", "key-2"));
|
||||
|
||||
|
||||
@@ -1041,6 +1041,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsSuccessAndSetsTTL() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1050,6 +1051,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
// missing field
|
||||
@@ -1059,6 +1061,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1066,6 +1069,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsSuccessAndSetsTTL() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1075,6 +1079,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
// missing field
|
||||
@@ -1084,6 +1089,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1091,6 +1097,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAtReturnsSuccessAndSetsTTL() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
@@ -1099,6 +1106,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
@@ -1110,6 +1118,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAdReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1117,6 +1126,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAtReturnsSuccessAndSetsTTL() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
@@ -1126,6 +1136,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
@@ -1137,6 +1148,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAdReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1144,6 +1156,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsSuccessAndPersistsField() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
assertThat(clusterConnection.hashCommands().hExpire(KEY_1_BYTES, 5L, KEY_2_BYTES)).contains(1L);
|
||||
@@ -1152,12 +1165,14 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsMinusOneWhenFieldDoesNotHaveExpiration() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
assertThat(clusterConnection.hashCommands().hPersist(KEY_1_BYTES, KEY_2_BYTES)).contains(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1167,14 +1182,15 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hTtlReturnsMinusOneWhenFieldHasNoExpiration() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_1_BYTES, KEY_2_BYTES)).contains(-1L);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hTtlReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_1_BYTES, KEY_1_BYTES)).contains(-2L);
|
||||
|
||||
@@ -1098,7 +1098,9 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsSuccessAndSetsTTL() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hExpire(KEY_1_BYTES, 5L, KEY_2_BYTES)).contains(1L);
|
||||
@@ -1106,7 +1108,9 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
// missing field
|
||||
assertThat(clusterConnection.hashCommands().hExpire(KEY_1_BYTES, 5L, KEY_1_BYTES)).contains(-2L);
|
||||
@@ -1115,6 +1119,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1122,16 +1127,19 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsSuccessAndSetsTTL() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hpExpire(KEY_1_BYTES, 5000L, KEY_2_BYTES)).contains(1L);
|
||||
assertThat(clusterConnection.hTtl(KEY_1_BYTES, TimeUnit.MILLISECONDS,KEY_2_BYTES))
|
||||
assertThat(clusterConnection.hTtl(KEY_1_BYTES, TimeUnit.MILLISECONDS, KEY_2_BYTES))
|
||||
.allSatisfy(val -> assertThat(val).isBetween(0L, 5000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
// missing field
|
||||
assertThat(clusterConnection.hashCommands().hpExpire(KEY_1_BYTES, 5L, KEY_1_BYTES)).contains(-2L);
|
||||
@@ -1140,14 +1148,18 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireReturnsTwoWhenZeroProvided() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hpExpire(KEY_1_BYTES, 0L, KEY_2_BYTES)).contains(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAtReturnsSuccessAndSetsTTL() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
assertThat(clusterConnection.hashCommands().hExpireAt(KEY_1_BYTES, inFiveSeconds, KEY_2_BYTES)).contains(1L);
|
||||
@@ -1155,17 +1167,21 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).getEpochSecond();
|
||||
|
||||
// missing field
|
||||
assertThat(clusterConnection.hashCommands().hExpireAt(KEY_1_BYTES, inFiveSeconds, KEY_1_BYTES)).contains(-2L);
|
||||
|
||||
// missing key
|
||||
assertThat(clusterConnection.hashCommands().hExpireAt(KEY_2_BYTES, inFiveSeconds, KEY_2_BYTES)).contains(-2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hExpireAdReturnsTwoWhenZeroProvided() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1173,16 +1189,20 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAtReturnsSuccessAndSetsTTL() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
assertThat(clusterConnection.hashCommands().hpExpireAt(KEY_1_BYTES, inFiveSeconds, KEY_2_BYTES)).contains(1L);
|
||||
assertThat(clusterConnection.hTtl(KEY_1_BYTES, TimeUnit.MILLISECONDS, KEY_2_BYTES))
|
||||
.allSatisfy(val -> assertThat(val).isBetween(0L, 5000L));
|
||||
assertThat(clusterConnection.hpTtl(KEY_1_BYTES, KEY_2_BYTES))
|
||||
.allSatisfy(val -> assertThat(val).isGreaterThan(1000L).isLessThanOrEqualTo(5000L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAtReturnsMinusTwoWhenFieldDoesNotExist() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
long inFiveSeconds = Instant.now().plusSeconds(5L).toEpochMilli();
|
||||
|
||||
@@ -1193,14 +1213,18 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hpExpireAdReturnsTwoWhenZeroProvided() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hpExpireAt(KEY_1_BYTES, 0L, KEY_2_BYTES)).contains(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsSuccessAndPersistsField() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
assertThat(clusterConnection.hashCommands().hExpire(KEY_1_BYTES, 5L, KEY_2_BYTES)).contains(1L);
|
||||
assertThat(clusterConnection.hashCommands().hPersist(KEY_1_BYTES, KEY_2_BYTES)).contains(1L);
|
||||
@@ -1208,12 +1232,15 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsMinusOneWhenFieldDoesNotHaveExpiration() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
assertThat(clusterConnection.hashCommands().hPersist(KEY_1_BYTES, KEY_2_BYTES)).contains(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hPersistReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
@@ -1223,19 +1250,21 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hTtlReturnsMinusOneWhenFieldHasNoExpiration() {
|
||||
|
||||
nativeConnection.hset(KEY_1, KEY_2, VALUE_3);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_1_BYTES, KEY_2_BYTES)).contains(-1L);
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_1_BYTES, TimeUnit.HOURS, KEY_2_BYTES)).contains(-1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
public void hTtlReturnsMinusTwoWhenFieldOrKeyMissing() {
|
||||
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_1_BYTES, KEY_1_BYTES)).contains(-2L);
|
||||
assertThat(clusterConnection.hashCommands().hTtl(KEY_3_BYTES,KEY_2_BYTES)).contains(-2L);
|
||||
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-315
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.redis.connection.lettuce;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.springframework.data.redis.test.condition.EnabledOnCommand;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -293,6 +294,7 @@ public class LettuceReactiveHashCommandsIntegrationTests extends LettuceReactive
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void hExpireShouldHandleMultipleParametersCorrectly() {
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_1, VALUE_1)).isTrue();
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_2, VALUE_2)).isTrue();
|
||||
@@ -312,6 +314,7 @@ public class LettuceReactiveHashCommandsIntegrationTests extends LettuceReactive
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void hExpireAtShouldHandleMultipleParametersCorrectly() {
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_1, VALUE_1)).isTrue();
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_2, VALUE_2)).isTrue();
|
||||
@@ -330,6 +333,7 @@ public class LettuceReactiveHashCommandsIntegrationTests extends LettuceReactive
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void hPersistShouldPersistFields() {
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_1, VALUE_1)).isTrue();
|
||||
assertThat(nativeCommands.hset(KEY_1, FIELD_2, VALUE_2)).isTrue();
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
@@ -27,13 +28,18 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.ObjectFactory;
|
||||
import org.springframework.data.redis.RawObjectFactory;
|
||||
import org.springframework.data.redis.StringObjectFactory;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.extension.JedisConnectionFactoryExtension;
|
||||
import org.springframework.data.redis.core.Expirations.Expiration;
|
||||
import org.springframework.data.redis.core.ExpireChanges.ExpiryChangeState;
|
||||
import org.springframework.data.redis.test.condition.EnabledOnCommand;
|
||||
import org.springframework.data.redis.test.extension.RedisStanalone;
|
||||
import org.springframework.data.redis.test.extension.parametrized.MethodSource;
|
||||
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
|
||||
@@ -136,7 +142,6 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
|
||||
long count = 0;
|
||||
try (Cursor<Map.Entry<HK, HV>> it = hashOps.scan(key, ScanOptions.scanOptions().count(1).build())) {
|
||||
|
||||
@@ -208,6 +213,7 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
assertThat(values).hasSize(2).containsEntry(key1, val1).containsEntry(key2, val2);
|
||||
}
|
||||
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
@ParameterizedRedisTest
|
||||
void testExpireAndGetExpireMillis() {
|
||||
|
||||
@@ -220,13 +226,20 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().expire(key, Duration.ofMillis(500), List.of(key1)))
|
||||
.containsExactly(1L);
|
||||
.satisfies(ExpireChanges::allOk);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, List.of(key1)))
|
||||
.allSatisfy(it -> assertThat(it).isBetween(0L, 500L));
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, List.of(key1))).satisfies(expirations -> {
|
||||
|
||||
assertThat(expirations.missing()).isEmpty();
|
||||
assertThat(expirations.precision()).isEqualTo(TimeUnit.SECONDS);
|
||||
assertThat(expirations.expirationOf(key1)).extracting(Expiration::raw, InstanceOfAssertFactories.LONG)
|
||||
.isBetween(0L, 1L);
|
||||
assertThat(expirations.ttlOf(key1)).isBetween(Duration.ZERO, Duration.ofSeconds(1));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireAndGetExpireSeconds() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
@@ -238,13 +251,26 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().expire(key, Duration.ofSeconds(5), List.of(key1, key2)))
|
||||
.containsExactly(1L, 1L);
|
||||
.satisfies(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
assertThat(changes.stateOf(key1)).isEqualTo(ExpiryChangeState.OK);
|
||||
assertThat(changes.ok()).containsExactlyInAnyOrder(key1, key2);
|
||||
assertThat(changes.missed()).isEmpty();
|
||||
assertThat(changes.stateChanges()).map(ExpiryChangeState::value).containsExactly(1L, 1L);
|
||||
});
|
||||
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, TimeUnit.SECONDS, List.of(key1, key2)))
|
||||
.allSatisfy(it -> assertThat(it).isBetween(0L, 5L));
|
||||
.satisfies(expirations -> {
|
||||
assertThat(expirations.missing()).isEmpty();
|
||||
assertThat(expirations.precision()).isEqualTo(TimeUnit.SECONDS);
|
||||
assertThat(expirations.expirationOf(key1)).extracting(Expiration::raw, InstanceOfAssertFactories.LONG)
|
||||
.isBetween(0L, 5L);
|
||||
assertThat(expirations.ttlOf(key1)).isBetween(Duration.ofSeconds(1), Duration.ofSeconds(5));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireAtAndGetExpireMillis() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
@@ -256,13 +282,69 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().expireAt(key, Instant.now().plusMillis(500), List.of(key1, key2)))
|
||||
.containsExactly(1L, 1L);
|
||||
.satisfies(ExpireChanges::allOk);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, List.of(key1, key2)))
|
||||
.allSatisfy(it -> assertThat(it).isBetween(0L, 500L));
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, TimeUnit.MILLISECONDS, List.of(key1, key2)))
|
||||
.satisfies(expirations -> {
|
||||
assertThat(expirations.missing()).isEmpty();
|
||||
assertThat(expirations.precision()).isEqualTo(TimeUnit.MILLISECONDS);
|
||||
assertThat(expirations.expirationOf(key1)).extracting(Expiration::raw, InstanceOfAssertFactories.LONG)
|
||||
.isBetween(0L, 500L);
|
||||
assertThat(expirations.ttlOf(key1)).isBetween(Duration.ZERO, Duration.ofMillis(500));
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
void expireThrowsErrorOfNanoPrecision() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> redisTemplate.opsForHash().getExpire(key, TimeUnit.NANOSECONDS, List.of(key1)));
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
void testExpireWithOptionsNone() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
ExpireChanges<Object> expire = redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20), FieldExpirationOptions.none(), List.of(key1));
|
||||
|
||||
assertThat(expire.allOk()).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireWithOptions() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20), FieldExpirationOptions.none(), List.of(key1));
|
||||
redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(60), FieldExpirationOptions.none(), List.of(key2));
|
||||
|
||||
ExpireChanges<Object> changes = redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(30), FieldExpirationOptions.builder().gt().build(), List.of(key1, key2));
|
||||
|
||||
assertThat(changes.ok()).containsExactly(key1);
|
||||
assertThat(changes.skipped()).containsExactly(key2);
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testPersistAndGetExpireMillis() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
@@ -273,13 +355,14 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
|
||||
hashOps.put(key, key1, val1);
|
||||
hashOps.put(key, key2, val2);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().expireAt(key, Instant.now().plusMillis(500), List.of(key1, key2)))
|
||||
.containsExactly(1L, 1L);
|
||||
assertThat(redisTemplate.opsForHash().expireAt(key, Instant.now().plusMillis(800), List.of(key1, key2)))
|
||||
.satisfies(ExpireChanges::allOk);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().persist(key, List.of(key1, key2)))
|
||||
.allSatisfy(it -> assertThat(it).isEqualTo(1L));
|
||||
assertThat(redisTemplate.opsForHash().persist(key, List.of(key2))).satisfies(ExpireChanges::allOk);
|
||||
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, List.of(key1, key2)))
|
||||
.allSatisfy(it -> assertThat(it).isEqualTo(-1L));
|
||||
assertThat(redisTemplate.opsForHash().getExpire(key, List.of(key1, key2))).satisfies(expirations -> {
|
||||
assertThat(expirations.expirationOf(key1).isPersistent()).isFalse();
|
||||
assertThat(expirations.expirationOf(key2).isPersistent()).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,28 +15,32 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
import static org.junit.jupiter.api.condition.OS.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
import static org.junit.jupiter.api.condition.OS.MAC;
|
||||
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.springframework.data.redis.ObjectFactory;
|
||||
import org.springframework.data.redis.RawObjectFactory;
|
||||
import org.springframework.data.redis.SettingsUtils;
|
||||
import org.springframework.data.redis.StringObjectFactory;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.convert.Converters;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
@@ -501,6 +505,141 @@ public class DefaultReactiveHashOperationsIntegrationTests<K, HK, HV> {
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
@ParameterizedRedisTest
|
||||
void testExpireAndGetExpireMillis() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, key1, val1, key2, val2);
|
||||
|
||||
hashOperations.expire(key, Duration.ofMillis(1500), List.of(key1)) //
|
||||
.as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
hashOperations.getExpire(key, List.of(key1)) //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(it -> {
|
||||
assertThat(it.expirationOf(key1).raw()).isBetween(0L, 2L);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireWithOptions() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, key1, val1, key2, val2);
|
||||
|
||||
hashOperations.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20), FieldExpirationOptions.none(), List.of(key1)).as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
hashOperations.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(60), FieldExpirationOptions.none(), List.of(key2)).as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
hashOperations.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(30), FieldExpirationOptions.builder().gt().build(), List.of(key1, key2)).as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.ok()).containsExactly(key1);
|
||||
assertThat(changes.skipped()).containsExactly(key2);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireAndGetExpireSeconds() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, key1, val1, key2, val2);
|
||||
|
||||
hashOperations.expire(key, Duration.ofSeconds(5), List.of(key1, key2)) //
|
||||
.as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
hashOperations.getExpire(key, TimeUnit.SECONDS, List.of(key1, key2)) //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(it -> {
|
||||
assertThat(it.expirationOf(key1).raw()).isBetween(0L, 5L);
|
||||
assertThat(it.expirationOf(key2).raw()).isBetween(0L, 5L);
|
||||
}).verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireAtAndGetExpireMillis() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, key1, val1, key2, val2);
|
||||
|
||||
redisTemplate.opsForHash().expireAt(key, Instant.now().plusMillis(1500), List.of(key1, key2))
|
||||
.as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
redisTemplate.opsForHash().getExpire(key, List.of(key1, key2)).as(StepVerifier::create)//
|
||||
.assertNext(it -> {
|
||||
assertThat(it.expirationOf(key1).raw()).isBetween(0L, 2L);
|
||||
assertThat(it.expirationOf(key2).raw()).isBetween(0L, 2L);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testPersistAndGetExpireMillis() {
|
||||
|
||||
K key = keyFactory.instance();
|
||||
HK key1 = hashKeyFactory.instance();
|
||||
HV val1 = hashValueFactory.instance();
|
||||
HK key2 = hashKeyFactory.instance();
|
||||
HV val2 = hashValueFactory.instance();
|
||||
|
||||
putAll(key, key1, val1, key2, val2);
|
||||
|
||||
redisTemplate.opsForHash().expireAt(key, Instant.now().plusMillis(1500), List.of(key1, key2))
|
||||
.as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
redisTemplate.opsForHash().persist(key, List.of(key1, key2)).as(StepVerifier::create)//
|
||||
.assertNext(changes -> {
|
||||
assertThat(changes.allOk()).isTrue();
|
||||
}).verifyComplete();
|
||||
|
||||
redisTemplate.opsForHash().getExpire(key, List.of(key1, key2)).as(StepVerifier::create)//
|
||||
.assertNext(expirations -> {
|
||||
assertThat(expirations.persistent()).contains(key1, key2);
|
||||
}).verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest // DATAREDIS-602
|
||||
void delete() {
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.springframework.data.redis.core.Expirations.Timeouts;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2025/02
|
||||
*/
|
||||
class ExpirationsUnitTest {
|
||||
|
||||
static final String KEY_1 = "key-1";
|
||||
static final String KEY_2 = "key-2";
|
||||
static final String KEY_3 = "key-3";
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(TimeUnit.class)
|
||||
void expirationMemorizesSourceUnit(TimeUnit targetUnit) {
|
||||
|
||||
Expirations<String> exp = Expirations.of(targetUnit, List.of(KEY_1), new Timeouts(TimeUnit.SECONDS, List.of(120L)));
|
||||
|
||||
assertThat(exp.expirations().get(0)).satisfies(expiration -> {
|
||||
assertThat(expiration.raw()).isEqualTo(120L);
|
||||
assertThat(expiration.value()).isEqualTo(targetUnit.convert(120, TimeUnit.SECONDS));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void expirationsCategorizesElements() {
|
||||
|
||||
Expirations<String> exp = createExpirations(new Timeouts(TimeUnit.SECONDS, List.of(-2L, -1L, 120L)));
|
||||
|
||||
assertThat(exp.persistent()).containsExactly(KEY_2);
|
||||
assertThat(exp.missing()).containsExactly(KEY_1);
|
||||
assertThat(exp.expiring()).containsExactly(Map.entry(KEY_3, Duration.ofMinutes(2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsNullForMissingElements() {
|
||||
|
||||
Expirations<String> exp = createExpirations(new Timeouts(TimeUnit.SECONDS, List.of(-2L, -1L, 120L)));
|
||||
|
||||
assertThat(exp.expirationOf("missing")).isNull();
|
||||
assertThat(exp.ttlOf("missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ttlReturnsDurationForEntriesWithTimeout() {
|
||||
|
||||
Expirations<String> exp = createExpirations(new Timeouts(TimeUnit.SECONDS, List.of(-2L, -1L, 120L)));
|
||||
|
||||
assertThat(exp.ttlOf(KEY_3)).isEqualTo(Duration.ofMinutes(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ttlReturnsNullForPersistentAndMissingEntries() {
|
||||
|
||||
Expirations<String> exp = createExpirations(new Timeouts(TimeUnit.SECONDS, List.of(-2L, -1L, 120L)));
|
||||
|
||||
assertThat(exp.ttlOf(KEY_1)).isNull();
|
||||
assertThat(exp.ttlOf(KEY_2)).isNull();
|
||||
}
|
||||
|
||||
static Expirations<String> createExpirations(Timeouts timeouts) {
|
||||
|
||||
List<String> keys = IntStream.range(1, timeouts.raw().size() + 1).mapToObj("key-%s"::formatted).toList();
|
||||
return Expirations.of(timeouts.timeUnit(), keys, timeouts);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.redis.support.collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
@@ -41,6 +42,7 @@ import org.springframework.data.redis.LongAsStringObjectFactory;
|
||||
import org.springframework.data.redis.ObjectFactory;
|
||||
import org.springframework.data.redis.RawObjectFactory;
|
||||
import org.springframework.data.redis.RedisSystemException;
|
||||
import org.springframework.data.redis.core.ExpireChanges;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
@@ -195,31 +197,41 @@ public abstract class AbstractRedisMapIntegrationTests<K, V> {
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpire() {
|
||||
|
||||
K k1 = getKey();
|
||||
V v1 = getValue();
|
||||
assertThat(map.put(k1, v1)).isEqualTo(null);
|
||||
|
||||
Collection<K> keys = Collections.singletonList(k1);
|
||||
assertThat(map.expire(Duration.ofSeconds(5), keys)).contains(1L);
|
||||
assertThat(map.getExpire(keys)).allSatisfy(expiration -> assertThat(expiration).isBetween(1L, 5L));
|
||||
assertThat(map.getExpire(TimeUnit.MILLISECONDS, keys))
|
||||
.allSatisfy(expiration -> assertThat(expiration).isBetween(1000L, 5000L));
|
||||
assertThat(map.persist(keys)).contains(1L);
|
||||
assertThat(map.expire(Duration.ofSeconds(5), keys)).satisfies(ExpireChanges::allOk);
|
||||
assertThat(map.getExpire(keys)).satisfies(expiration -> {
|
||||
assertThat(expiration.expirationOf(k1).raw()).isBetween(1L, 5L);
|
||||
});
|
||||
assertThat(map.getExpire(TimeUnit.MILLISECONDS, keys)).satisfies(expiration -> {
|
||||
assertThat(expiration.expirationOf(k1).raw()).isBetween(1000L, 5000L);
|
||||
});
|
||||
assertThat(map.persist(keys)).satisfies(ExpireChanges::allOk);
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
@EnabledOnCommand("HEXPIRE")
|
||||
void testExpireAt() {
|
||||
|
||||
K k1 = getKey();
|
||||
V v1 = getValue();
|
||||
assertThat(map.put(k1, v1)).isEqualTo(null);
|
||||
|
||||
Collection<K> keys = Collections.singletonList(k1);
|
||||
assertThat(map.expireAt(Instant.now().plusSeconds(5), keys)).contains(1L);
|
||||
assertThat(map.getExpire(keys)).allSatisfy(expiration -> assertThat(expiration).isBetween(1L, 5L));
|
||||
assertThat(map.getExpire(TimeUnit.MILLISECONDS, keys))
|
||||
.allSatisfy(expiration -> assertThat(expiration).isBetween(1000L, 5000L));
|
||||
assertThat(map.persist(keys)).contains(1L);
|
||||
assertThat(map.expireAt(Instant.now().plusSeconds(5), keys)).satisfies(ExpireChanges::allOk);
|
||||
assertThat(map.getExpire(keys)).satisfies(expiration -> {
|
||||
assertThat(expiration.expirationOf(k1).raw()).isBetween(1L, 5L);
|
||||
});
|
||||
assertThat(map.getExpire(TimeUnit.MILLISECONDS, keys)).satisfies(expiration -> {
|
||||
assertThat(expiration.expirationOf(k1).raw()).isBetween(1000L, 5000L);
|
||||
});
|
||||
assertThat(map.persist(keys)).satisfies(ExpireChanges::allOk);
|
||||
}
|
||||
|
||||
@ParameterizedRedisTest
|
||||
|
||||
Reference in New Issue
Block a user