Add support for conditional key expiry (XX/NX/LT/GT).

Move ExpirationOptions from Hash to top-level.

Closes: #3114
Original Pull Request: #3115
This commit is contained in:
Mark Paluch
2025-03-05 16:40:00 +01:00
committed by Christoph Strobl
parent 08d66aa10d
commit b8d289208e
43 changed files with 1198 additions and 553 deletions

View File

@@ -31,7 +31,6 @@ 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;
@@ -359,13 +358,13 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
}
@Override
public Boolean expire(byte[] key, long seconds) {
return convertAndReturn(delegate.expire(key, seconds), Converters.identityConverter());
public Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
return convertAndReturn(delegate.expire(key, seconds, condition), Converters.identityConverter());
}
@Override
public Boolean expireAt(byte[] key, long unixTime) {
return convertAndReturn(delegate.expireAt(key, unixTime), Converters.identityConverter());
public Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
return convertAndReturn(delegate.expireAt(key, unixTime, condition), Converters.identityConverter());
}
@Override
@@ -1308,13 +1307,13 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
}
@Override
public Boolean pExpire(byte[] key, long millis) {
return convertAndReturn(delegate.pExpire(key, millis), Converters.identityConverter());
public Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
return convertAndReturn(delegate.pExpire(key, millis, condition), Converters.identityConverter());
}
@Override
public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
return convertAndReturn(delegate.pExpireAt(key, unixTimeInMillis), Converters.identityConverter());
public Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
return convertAndReturn(delegate.pExpireAt(key, unixTimeInMillis, condition), Converters.identityConverter());
}
@Override
@@ -1497,13 +1496,13 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
}
@Override
public Boolean expire(String key, long seconds) {
return expire(serialize(key), seconds);
public Boolean expire(String key, long seconds, ExpirationOptions.Condition condition) {
return expire(serialize(key), seconds, condition);
}
@Override
public Boolean expireAt(String key, long unixTime) {
return expireAt(serialize(key), unixTime);
public Boolean expireAt(String key, long unixTime, ExpirationOptions.Condition condition) {
return expireAt(serialize(key), unixTime, condition);
}
@Override
@@ -2491,13 +2490,13 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
}
@Override
public Boolean pExpire(String key, long millis) {
return pExpire(serialize(key), millis);
public Boolean pExpire(String key, long millis, ExpirationOptions.Condition condition) {
return pExpire(serialize(key), millis, condition);
}
@Override
public Boolean pExpireAt(String key, long unixTimeInMillis) {
return pExpireAt(serialize(key), unixTimeInMillis);
public Boolean pExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
return pExpireAt(serialize(key), unixTimeInMillis, condition);
}
@Override
@@ -2581,29 +2580,29 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
return convertAndReturn(delegate.hStrLen(key, field), Converters.identityConverter());
}
public @Nullable List<Long> applyExpiration(byte[] key,
public @Nullable List<Long> applyHashFieldExpiration(byte[] key,
org.springframework.data.redis.core.types.Expiration expiration,
FieldExpirationOptions options, byte[]... fields) {
return this.delegate.applyExpiration(key, expiration, options, fields);
ExpirationOptions options, byte[]... fields) {
return this.delegate.applyHashFieldExpiration(key, expiration, options, fields);
}
@Override
public List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
return this.delegate.hExpire(key, seconds, condition, fields);
}
@Override
public List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
return this.delegate.hpExpire(key, millis, condition, fields);
}
@Override
public List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) {
return this.delegate.hExpireAt(key, unixTime, condition, fields);
}
@Override
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields) {
return this.delegate.hpExpireAt(key, unixTimeInMillis, condition, fields);
}
@@ -2630,27 +2629,27 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco
public @Nullable List<Long> applyExpiration(String key,
org.springframework.data.redis.core.types.Expiration expiration,
FieldExpirationOptions options, String... fields) {
return applyExpiration(serialize(key), expiration, options, serializeMulti(fields));
ExpirationOptions options, String... fields) {
return this.applyHashFieldExpiration(serialize(key), expiration, options, serializeMulti(fields));
}
@Override
public List<Long> hExpire(String key, long seconds, FieldExpirationOptions.Condition condition, String... fields) {
public List<Long> hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields) {
return hExpire(serialize(key), seconds, condition, serializeMulti(fields));
}
@Override
public List<Long> hpExpire(String key, long millis, FieldExpirationOptions.Condition condition, String... fields) {
public List<Long> hpExpire(String key, long millis, ExpirationOptions.Condition condition, String... fields) {
return hpExpire(serialize(key), millis, condition, serializeMulti(fields));
}
@Override
public List<Long> hExpireAt(String key, long unixTime, FieldExpirationOptions.Condition condition, String... fields) {
public List<Long> hExpireAt(String key, long unixTime, ExpirationOptions.Condition condition, String... fields) {
return hExpireAt(serialize(key), unixTime, condition, serializeMulti(fields));
}
@Override
public List<Long> hpExpireAt(String key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
public List<Long> hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition,
String... fields) {
return hpExpireAt(serialize(key), unixTimeInMillis, condition, serializeMulti(fields));
}

View File

@@ -28,7 +28,6 @@ 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;
@@ -162,7 +161,14 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default Boolean expire(byte[] key, long seconds) {
return keyCommands().expire(key, seconds);
return keyCommands().expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
return keyCommands().expire(key, seconds, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@@ -204,21 +210,42 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default Boolean pExpire(byte[] key, long millis) {
return keyCommands().pExpire(key, millis);
return keyCommands().pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
return keyCommands().pExpire(key, millis, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
return keyCommands().pExpireAt(key, unixTimeInMillis);
return keyCommands().pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
return keyCommands().pExpireAt(key, unixTimeInMillis, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean expireAt(byte[] key, long unixTime) {
return keyCommands().expireAt(key, unixTime);
return keyCommands().expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@Override
@Deprecated
default Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
return keyCommands().expireAt(key, unixTime, condition);
}
/** @deprecated in favor of {@link RedisConnection#keyCommands()}. */
@@ -1483,13 +1510,13 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default List<Long> hExpire(byte[] key, long seconds, byte[]... fields) {
return hashCommands().hExpire(key, seconds, FieldExpirationOptions.Condition.ALWAYS, fields);
return hashCommands().hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) {
default List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
return hashCommands().hExpire(key, seconds, condition, fields);
}
@@ -1497,13 +1524,13 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default List<Long> hpExpire(byte[] key, long millis, byte[]... fields) {
return hashCommands().hpExpire(key, millis, FieldExpirationOptions.Condition.ALWAYS, fields);
return hashCommands().hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) {
default List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
return hashCommands().hpExpire(key, millis, condition, fields);
}
@@ -1511,13 +1538,13 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default List<Long> hExpireAt(byte[] key, long unixTime, byte[]... fields) {
return hashCommands().hExpireAt(key, unixTime, FieldExpirationOptions.Condition.ALWAYS, fields);
return hashCommands().hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition,
default List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition,
byte[]... fields) {
return hashCommands().hExpireAt(key, unixTime, condition, fields);
}
@@ -1526,13 +1553,13 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
@Override
@Deprecated
default List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields) {
return hashCommands().hpExpireAt(key, unixTimeInMillis, FieldExpirationOptions.Condition.ALWAYS, fields);
return hashCommands().hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
}
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
default List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields) {
return hashCommands().hpExpireAt(key, unixTimeInMillis, condition, fields);
}
@@ -1568,10 +1595,10 @@ public interface DefaultedRedisConnection extends RedisCommands, RedisCommandsPr
/** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */
@Override
@Deprecated
default @Nullable List<Long> applyExpiration(byte[] key,
org.springframework.data.redis.core.types.Expiration expiration, FieldExpirationOptions options,
default @Nullable List<Long> applyHashFieldExpiration(byte[] key,
org.springframework.data.redis.core.types.Expiration expiration, ExpirationOptions options,
byte[]... fields) {
return hashCommands().applyExpiration(key, expiration, options, fields);
return hashCommands().applyHashFieldExpiration(key, expiration, options, fields);
}
// GEO COMMANDS

View File

@@ -0,0 +1,145 @@
/*
* 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.util.ObjectUtils;
/**
* Expiration options for Expiation updates.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.5
*/
public class ExpirationOptions {
private static final ExpirationOptions NONE = new ExpirationOptions(Condition.ALWAYS);
private final Condition condition;
ExpirationOptions(Condition condition) {
this.condition = condition;
}
/**
* @return an empty expiration options object.
*/
public static ExpirationOptions none() {
return NONE;
}
/**
* @return builder for creating {@code FieldExpireOptionsBuilder}.
*/
public static FieldExpireOptionsBuilder builder() {
return new FieldExpireOptionsBuilder();
}
public Condition getCondition() {
return condition;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExpirationOptions that = (ExpirationOptions) o;
return ObjectUtils.nullSafeEquals(this.condition, that.condition);
}
@Override
public int hashCode() {
return Objects.hash(condition);
}
/**
* Builder to build {@link ExpirationOptions}
*/
public static class FieldExpireOptionsBuilder {
private Condition condition = Condition.ALWAYS;
private FieldExpireOptionsBuilder() {}
@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;
}
public ExpirationOptions build() {
return condition == Condition.ALWAYS ? NONE : new ExpirationOptions(condition);
}
}
/**
* Conditions to apply when changing expiration.
*/
public enum Condition {
/**
* Always apply expiration.
*/
ALWAYS,
/**
* 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
}
}

View File

@@ -1,137 +0,0 @@
/*
* 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.util.ObjectUtils;
/**
* Types for interacting with Hash data structures.
*
* @author Christoph Strobl
* @since 3.5
*/
public interface Hash {
/**
* Expiration options for Hash Expiation updates.
*/
class FieldExpirationOptions {
private static final FieldExpirationOptions NONE = new FieldExpirationOptions(Condition.ALWAYS);
private final Condition condition;
FieldExpirationOptions(Condition condition) {
this.condition = condition;
}
public static FieldExpirationOptions none() {
return NONE;
}
public static FieldExpireOptionsBuilder builder() {
return new FieldExpireOptionsBuilder();
}
public 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 {
private Condition condition = Condition.ALWAYS;
@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;
}
public FieldExpirationOptions build() {
return condition == Condition.ALWAYS ? NONE : new FieldExpirationOptions(condition);
}
}
public enum Condition {
/**
* Always apply expiration.
*/
ALWAYS,
/**
* 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
}
}
}

View File

@@ -31,7 +31,6 @@ 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;
@@ -851,13 +850,13 @@ public interface ReactiveHashCommands {
/**
* @since 3.5
*/
class ExpireCommand extends HashFieldsCommand {
class HashExpireCommand extends HashFieldsCommand {
private final Expiration expiration;
private final FieldExpirationOptions options;
private final ExpirationOptions options;
private ExpireCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields, Expiration expiration,
FieldExpirationOptions options) {
private HashExpireCommand(@Nullable ByteBuffer key, List<ByteBuffer> fields, Expiration expiration,
ExpirationOptions options) {
super(key, fields);
@@ -866,52 +865,52 @@ public interface ReactiveHashCommands {
}
/**
* Creates a new {@link ExpireCommand}.
* Creates a new {@link HashExpireCommand}.
*
* @param fields the {@code field} names to apply expiration to
* @param timeout the actual timeout
* @param unit the unit of measure for the {@code timeout}.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public static ExpireCommand expire(List<ByteBuffer> fields, long timeout, TimeUnit unit) {
public static HashExpireCommand expire(List<ByteBuffer> fields, long timeout, TimeUnit unit) {
Assert.notNull(fields, "Field must not be null");
return expire(fields, Expiration.from(timeout, unit));
}
/**
* Creates a new {@link ExpireCommand}.
* Creates a new {@link HashExpireCommand}.
*
* @param fields the {@code field} names to apply expiration to.
* @param ttl the actual timeout.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public static ExpireCommand expire(List<ByteBuffer> fields, Duration ttl) {
public static HashExpireCommand expire(List<ByteBuffer> fields, Duration ttl) {
Assert.notNull(fields, "Field must not be null");
return expire(fields, Expiration.from(ttl));
}
/**
* Creates a new {@link ExpireCommand}.
* Creates a new {@link HashExpireCommand}.
*
* @param fields the {@code field} names to apply expiration to
* @param expiration the {@link Expiration} to apply to the given {@literal fields}.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public static ExpireCommand expire(List<ByteBuffer> fields, Expiration expiration) {
return new ExpireCommand(null, fields, expiration, FieldExpirationOptions.none());
public static HashExpireCommand expire(List<ByteBuffer> fields, Expiration expiration) {
return new HashExpireCommand(null, fields, expiration, ExpirationOptions.none());
}
/**
* Creates a new {@link ExpireCommand}.
* Creates a new {@link HashExpireCommand}.
*
* @param fields the {@code field} names to apply expiration to
* @param ttl the unix point in time when to expire the given {@literal fields}.
* @param precision can be {@link TimeUnit#SECONDS} or {@link TimeUnit#MILLISECONDS}.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public static ExpireCommand expireAt(List<ByteBuffer> fields, Instant ttl, TimeUnit precision) {
public static HashExpireCommand expireAt(List<ByteBuffer> fields, Instant ttl, TimeUnit precision) {
if (precision.compareTo(TimeUnit.MILLISECONDS) > 0) {
return expire(fields, Expiration.unixTimestamp(ttl.getEpochSecond(), TimeUnit.SECONDS));
@@ -922,25 +921,25 @@ public interface ReactiveHashCommands {
/**
* @param key the {@literal key} from which to expire the {@literal fields} from.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public ExpireCommand from(ByteBuffer key) {
return new ExpireCommand(key, getFields(), expiration, options);
public HashExpireCommand from(ByteBuffer key) {
return new HashExpireCommand(key, getFields(), expiration, options);
}
/**
* @param options additional options to be sent along with the command.
* @return new instance of {@link ExpireCommand}.
* @return new instance of {@link HashExpireCommand}.
*/
public ExpireCommand withOptions(FieldExpirationOptions options) {
return new ExpireCommand(getKey(), getFields(), getExpiration(), options);
public HashExpireCommand withOptions(ExpirationOptions options) {
return new HashExpireCommand(getKey(), getFields(), getExpiration(), options);
}
public Expiration getExpiration() {
return expiration;
}
public FieldExpirationOptions getOptions() {
public ExpirationOptions getOptions() {
return options;
}
}
@@ -983,7 +982,7 @@ public interface ReactiveHashCommands {
Assert.notNull(duration, "Duration must not be null");
return applyExpiration(Flux.just(ExpireCommand.expire(fields, duration).from(key)))
return applyHashFieldExpiration(Flux.just(HashExpireCommand.expire(fields, duration).from(key)))
.mapNotNull(NumericResponse::getOutput);
}
@@ -999,7 +998,7 @@ public interface ReactiveHashCommands {
* @since 3.5
* @see <a href="https://redis.io/commands/hexpire">Redis Documentation: HEXPIRE</a>
*/
Flux<NumericResponse<ExpireCommand, Long>> applyExpiration(Publisher<ExpireCommand> commands);
Flux<NumericResponse<HashExpireCommand, Long>> applyHashFieldExpiration(Publisher<HashExpireCommand> commands);
/**
* Expire a given {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed.
@@ -1039,8 +1038,8 @@ public interface ReactiveHashCommands {
Assert.notNull(duration, "Duration must not be null");
return applyExpiration(Flux.just(new ExpireCommand(key, fields,
Expiration.from(duration.toMillis(), TimeUnit.MILLISECONDS), FieldExpirationOptions.none())))
return applyHashFieldExpiration(Flux.just(new HashExpireCommand(key, fields,
Expiration.from(duration.toMillis(), TimeUnit.MILLISECONDS), ExpirationOptions.none())))
.mapNotNull(NumericResponse::getOutput);
}
@@ -1083,7 +1082,7 @@ public interface ReactiveHashCommands {
Assert.notNull(expireAt, "Duration must not be null");
return applyExpiration(Flux.just(ExpireCommand.expireAt(fields, expireAt, TimeUnit.SECONDS).from(key)))
return applyHashFieldExpiration(Flux.just(HashExpireCommand.expireAt(fields, expireAt, TimeUnit.SECONDS).from(key)))
.mapNotNull(NumericResponse::getOutput);
}
@@ -1126,7 +1125,8 @@ public interface ReactiveHashCommands {
Assert.notNull(expireAt, "Duration must not be null");
return applyExpiration(Flux.just(ExpireCommand.expireAt(fields, expireAt, TimeUnit.MILLISECONDS).from(key)))
return applyHashFieldExpiration(
Flux.just(HashExpireCommand.expireAt(fields, expireAt, TimeUnit.MILLISECONDS).from(key)))
.mapNotNull(NumericResponse::getOutput);
}

View File

@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
@@ -32,6 +33,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnection.MultiVa
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.core.KeyScanOptions;
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;
@@ -513,13 +515,35 @@ public interface ReactiveKeyCommands {
*/
class ExpireCommand extends KeyCommand {
private @Nullable Duration timeout;
private final Expiration expiration;
private final ExpirationOptions options;
private ExpireCommand(ByteBuffer key, @Nullable Duration timeout) {
private ExpireCommand(ByteBuffer key, Duration timeout) {
this(key, Expiration.from(timeout), ExpirationOptions.none());
}
private ExpireCommand(@Nullable ByteBuffer key, Expiration expiration, ExpirationOptions options) {
super(key);
this.timeout = timeout;
this.expiration = expiration;
this.options = options;
}
/**
* Creates a new {@link ExpireCommand} given a {@link ByteBuffer key} and {@link Expiration}.
*
* @param key must not be {@literal null}.
* @param expiration must not be {@literal null}.
* @return a new {@link ExpireCommand} for {@link ByteBuffer key} and {@link Expiration}.
* @since 3.5
*/
public static ExpireCommand expire(ByteBuffer key, Expiration expiration) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(expiration, "Expiration must not be null");
return new ExpireCommand(key, expiration, ExpirationOptions.none());
}
/**
@@ -532,7 +556,7 @@ public interface ReactiveKeyCommands {
Assert.notNull(key, "Key must not be null");
return new ExpireCommand(key, null);
return new ExpireCommand(key, Expiration.persistent(), ExpirationOptions.none());
}
/**
@@ -545,7 +569,21 @@ public interface ReactiveKeyCommands {
Assert.notNull(timeout, "Timeout must not be null");
return new ExpireCommand(getKey(), timeout);
return new ExpireCommand(getKey(), Expiration.from(timeout), options);
}
/**
* Applies the {@literal timeout}. Constructs a new command instance with all previously configured properties.
*
* @param timeout must not be {@literal null}.
* @return a new {@link ExpireCommand} with {@literal timeout} applied.
* @since 3.5
*/
public ExpireCommand expire(Duration timeout) {
Assert.notNull(timeout, "Timeout must not be null");
return new ExpireCommand(getKey(), Expiration.from(timeout), options);
}
/**
@@ -553,10 +591,50 @@ public interface ReactiveKeyCommands {
*/
@Nullable
public Duration getTimeout() {
return timeout;
if (expiration.isUnixTimestamp() || expiration.isPersistent()) {
return null;
}
return Duration.ofMillis(expiration.getExpirationTimeInMilliseconds());
}
/**
* @param options additional options to be sent along with the command.
* @return new instance of {@link ExpireCommand}.
* @since 3.5
*/
public ExpireCommand withOptions(ExpirationOptions options) {
return new ExpireCommand(getKey(), getExpiration(), options);
}
public Expiration getExpiration() {
return expiration;
}
public ExpirationOptions getOptions() {
return options;
}
}
/**
* 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, {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @since 3.5
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see <a href="https://redis.io/commands/persist">Redis Documentation: PERSIST</a>
*/
Flux<BooleanResponse<ExpireCommand>> applyExpiration(Publisher<ExpireCommand> commands);
/**
* Set time to live for given {@code key} in seconds.
*
@@ -581,7 +659,9 @@ public interface ReactiveKeyCommands {
* result.
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
*/
Flux<BooleanResponse<ExpireCommand>> expire(Publisher<ExpireCommand> commands);
default Flux<BooleanResponse<ExpireCommand>> expire(Publisher<ExpireCommand> commands) {
return applyExpiration(commands);
}
/**
* Set time to live for given {@code key} in milliseconds.
@@ -607,7 +687,9 @@ public interface ReactiveKeyCommands {
* result.
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
*/
Flux<BooleanResponse<ExpireCommand>> pExpire(Publisher<ExpireCommand> commands);
default Flux<BooleanResponse<ExpireCommand>> pExpire(Publisher<ExpireCommand> commands) {
return applyExpiration(commands);
}
/**
* {@code EXPIREAT}/{@code PEXPIREAT} command parameters.
@@ -619,12 +701,18 @@ public interface ReactiveKeyCommands {
class ExpireAtCommand extends KeyCommand {
private @Nullable Instant expireAt;
private final ExpirationOptions options;
private ExpireAtCommand(ByteBuffer key, @Nullable Instant expireAt) {
private ExpireAtCommand(ByteBuffer key, Instant expireAt) {
this(key, expireAt, ExpirationOptions.none());
}
private ExpireAtCommand(@Nullable ByteBuffer key, Instant expireAt, ExpirationOptions options) {
super(key);
this.expireAt = expireAt;
this.options = options;
}
/**
@@ -653,6 +741,15 @@ public interface ReactiveKeyCommands {
return new ExpireAtCommand(getKey(), expireAt);
}
/**
* @param options additional options to be sent along with the command.
* @return new instance of {@link ExpireAtCommand}.
* @since 3.5
*/
public ExpireAtCommand withOptions(ExpirationOptions options) {
return new ExpireAtCommand(getKey(), getExpireAt(), options);
}
/**
* @return can be {@literal null}.
*/
@@ -660,6 +757,11 @@ public interface ReactiveKeyCommands {
public Instant getExpireAt() {
return expireAt;
}
public ExpirationOptions getOptions() {
return options;
}
}
/**

View File

@@ -21,7 +21,6 @@ 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;
@@ -257,7 +256,7 @@ public interface RedisHashCommands {
/**
* Apply a given {@link org.springframework.data.redis.core.types.Expiration} to the given {@literal fields}.
*
*
* @param key must not be {@literal null}.
* @param expiration the {@link org.springframework.data.redis.core.types.Expiration} to apply.
* @param fields the names of the {@literal fields} to apply the {@literal expiration} to.
@@ -267,9 +266,9 @@ public interface RedisHashCommands {
* such field;
* @since 3.5
*/
default @Nullable List<Long> applyExpiration(byte[] key,
default @Nullable List<Long> applyHashFieldExpiration(byte[] key,
org.springframework.data.redis.core.types.Expiration expiration, byte[]... fields) {
return applyExpiration(key, expiration, FieldExpirationOptions.none(), fields);
return applyHashFieldExpiration(key, expiration, ExpirationOptions.none(), fields);
}
/**
@@ -284,14 +283,14 @@ public interface RedisHashCommands {
* @since 3.5
*/
@Nullable
default List<Long> applyExpiration(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
FieldExpirationOptions options, byte[]... fields) {
default List<Long> applyHashFieldExpiration(byte[] key,
org.springframework.data.redis.core.types.Expiration expiration, ExpirationOptions options, byte[]... fields) {
if (expiration.isPersistent()) {
return hPersist(key, fields);
}
if (ObjectUtils.nullSafeEquals(FieldExpirationOptions.none(), options)) {
if (ObjectUtils.nullSafeEquals(ExpirationOptions.none(), options)) {
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
if (expiration.isUnixTimestamp()) {
return hpExpireAt(key, expiration.getExpirationTimeInMilliseconds(), fields);
@@ -334,7 +333,7 @@ public interface RedisHashCommands {
*/
@Nullable
default List<Long> hExpire(byte[] key, long seconds, byte[]... fields) {
return hExpire(key, seconds, FieldExpirationOptions.Condition.ALWAYS, fields);
return hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -372,7 +371,7 @@ public interface RedisHashCommands {
* @since 3.5
*/
@Nullable
List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields);
List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields);
/**
* Set time to live for given {@code fields} in milliseconds.
@@ -390,7 +389,7 @@ public interface RedisHashCommands {
*/
@Nullable
default List<Long> hpExpire(byte[] key, long millis, byte[]... fields) {
return hpExpire(key, millis, FieldExpirationOptions.Condition.ALWAYS, fields);
return hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -429,7 +428,7 @@ public interface RedisHashCommands {
* @since 3.5
*/
@Nullable
List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields);
List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields);
/**
* Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
@@ -446,7 +445,7 @@ public interface RedisHashCommands {
*/
@Nullable
default List<Long> hExpireAt(byte[] key, long unixTime, byte[]... fields) {
return hExpireAt(key, unixTime, FieldExpirationOptions.Condition.ALWAYS, fields);
return hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -465,7 +464,7 @@ public interface RedisHashCommands {
* @since 3.5
*/
@Nullable
List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields);
List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields);
/**
* Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
@@ -482,7 +481,7 @@ public interface RedisHashCommands {
*/
@Nullable
default List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... fields) {
return hpExpireAt(key, unixTimeInMillis, FieldExpirationOptions.Condition.ALWAYS, fields);
return hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -501,7 +500,7 @@ public interface RedisHashCommands {
* @since 3.5
*/
@Nullable
List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields);
/**

View File

@@ -26,6 +26,7 @@ import org.springframework.data.redis.core.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Key-specific commands supported by Redis.
@@ -181,23 +182,93 @@ public interface RedisKeyCommands {
@Nullable
Boolean renameNX(byte[] oldKey, byte[] newKey);
/**
* @param key must not be {@literal null}.
* @param expiration the {@link org.springframework.data.redis.core.types.Expiration} to apply.
* @param options additional options to be sent along with the command.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @since 3.5
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see <a href="https://redis.io/commands/persist">Redis Documentation: PERSIST</a>
*/
@Nullable
default Boolean applyExpiration(byte[] key, org.springframework.data.redis.core.types.Expiration expiration,
ExpirationOptions options) {
if (expiration.isPersistent()) {
return persist(key);
}
if (ObjectUtils.nullSafeEquals(ExpirationOptions.none(), options)) {
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
if (expiration.isUnixTimestamp()) {
return expireAt(key, expiration.getExpirationTimeInMilliseconds());
}
return expire(key, expiration.getExpirationTimeInMilliseconds());
}
if (expiration.isUnixTimestamp()) {
return expireAt(key, expiration.getExpirationTimeInSeconds());
}
return expire(key, expiration.getExpirationTimeInSeconds());
}
if (ObjectUtils.nullSafeEquals(TimeUnit.MILLISECONDS, expiration.getTimeUnit())) {
if (expiration.isUnixTimestamp()) {
return expireAt(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition());
}
return expire(key, expiration.getExpirationTimeInMilliseconds(), options.getCondition());
}
if (expiration.isUnixTimestamp()) {
return expireAt(key, expiration.getExpirationTimeInSeconds(), options.getCondition());
}
return expire(key, expiration.getExpirationTimeInSeconds(), options.getCondition());
}
/**
* Set time to live for given {@code key} in seconds.
*
* @param key must not be {@literal null}.
* @param seconds
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
*/
@Nullable
Boolean expire(byte[] key, long seconds);
default Boolean expire(byte[] key, long seconds) {
return expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set time to live for given {@code key} in seconds.
*
* @param key must not be {@literal null}.
* @param seconds
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @since 3.5
*/
@Nullable
Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition);
/**
* 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.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @since 3.5
*/
@@ -211,18 +282,38 @@ public interface RedisKeyCommands {
*
* @param key must not be {@literal null}.
* @param millis
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
*/
@Nullable
Boolean pExpire(byte[] key, long millis);
default Boolean pExpire(byte[] key, long millis) {
return pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set time to live for given {@code key} in milliseconds.
*
* @param key must not be {@literal null}.
* @param millis
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @since 3.5
*/
@Nullable
Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition);
/**
* 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.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @since 3.5
*/
@@ -236,11 +327,29 @@ public interface RedisKeyCommands {
*
* @param key must not be {@literal null}.
* @param unixTime
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
*/
@Nullable
Boolean expireAt(byte[] key, long unixTime);
default Boolean expireAt(byte[] key, long unixTime) {
return expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
*
* @param key must not be {@literal null}.
* @param unixTime
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @since 3.5
*/
@Nullable
Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition);
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#getEpochSecond() seconds}
@@ -248,7 +357,9 @@ public interface RedisKeyCommands {
*
* @param key must not be {@literal null}.
* @param unixTime
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @since 3.5
*/
@@ -262,11 +373,29 @@ public interface RedisKeyCommands {
*
* @param key must not be {@literal null}.
* @param unixTimeInMillis
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
*/
@Nullable
Boolean pExpireAt(byte[] key, long unixTimeInMillis);
default Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
return pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
*
* @param key must not be {@literal null}.
* @param unixTimeInMillis
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @since 3.5
*/
@Nullable
Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition);
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in {@link Instant#toEpochMilli()
@@ -274,7 +403,9 @@ public interface RedisKeyCommands {
*
* @param key must not be {@literal null}.
* @param unixTime
* @return {@literal null} when used in pipeline / transaction.
* @return {@literal null} when used in pipeline / transaction. {@literal true} if the timeout was set or
* {@literal false} if the timeout was not set; for example, the key doesn't exist, or the operation was
* skipped because of the provided arguments.
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @since 3.5
*/

View File

@@ -224,7 +224,22 @@ public interface StringRedisConnection extends RedisConnection {
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @see RedisKeyCommands#expire(byte[], long)
*/
Boolean expire(String key, long seconds);
default Boolean expire(String key, long seconds) {
return expire(key, seconds, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set time to live for given {@code key} in seconds.
*
* @param key must not be {@literal null}.
* @param condition the condition for expiration, must not be {@literal null}.
* @param seconds
* @return
* @since 3.5
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @see RedisKeyCommands#expire(byte[], long)
*/
Boolean expire(String key, long seconds, ExpirationOptions.Condition condition);
/**
* Set time to live for given {@code key} in milliseconds.
@@ -235,7 +250,22 @@ public interface StringRedisConnection extends RedisConnection {
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see RedisKeyCommands#pExpire(byte[], long)
*/
Boolean pExpire(String key, long millis);
default Boolean pExpire(String key, long millis) {
return pExpire(key, millis, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set time to live for given {@code key} in milliseconds.
*
* @param key must not be {@literal null}.
* @param millis
* @param condition the condition for expiration, must not be {@literal null}.
* @return
* @since 3.5
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see RedisKeyCommands#pExpire(byte[], long)
*/
Boolean pExpire(String key, long millis, ExpirationOptions.Condition condition);
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
@@ -246,7 +276,22 @@ public interface StringRedisConnection extends RedisConnection {
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see RedisKeyCommands#expireAt(byte[], long)
*/
Boolean expireAt(String key, long unixTime);
default Boolean expireAt(String key, long unixTime) {
return expireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp.
*
* @param key must not be {@literal null}.
* @param unixTime
* @param condition the condition for expiration, must not be {@literal null}.
* @return
* @since 3.5
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see RedisKeyCommands#expireAt(byte[], long)
*/
Boolean expireAt(String key, long unixTime, ExpirationOptions.Condition condition);
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
@@ -257,7 +302,22 @@ public interface StringRedisConnection extends RedisConnection {
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see RedisKeyCommands#pExpireAt(byte[], long)
*/
Boolean pExpireAt(String key, long unixTimeInMillis);
default Boolean pExpireAt(String key, long unixTimeInMillis) {
return pExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS);
}
/**
* Set the expiration for given {@code key} as a {@literal UNIX} timestamp in milliseconds.
*
* @param key must not be {@literal null}.
* @param unixTimeInMillis
* @param condition the condition for expiration, must not be {@literal null}.
* @return
* @since 3.5
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see RedisKeyCommands#pExpireAt(byte[], long)
*/
Boolean pExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition);
/**
* Remove the expiration from given {@code key}.
@@ -2348,7 +2408,7 @@ public interface StringRedisConnection extends RedisConnection {
*/
@Nullable
default List<Long> hExpire(String key, long seconds, String... fields) {
return hExpire(key, seconds, Hash.FieldExpirationOptions.Condition.ALWAYS, fields);
return hExpire(key, seconds, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -2366,7 +2426,7 @@ public interface StringRedisConnection extends RedisConnection {
* @since 3.5
*/
@Nullable
List<Long> hExpire(String key, long seconds, Hash.FieldExpirationOptions.Condition condition, String... fields);
List<Long> hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields);
/**
* Set time to live for given {@code field} in milliseconds.
@@ -2383,7 +2443,7 @@ public interface StringRedisConnection extends RedisConnection {
*/
@Nullable
default List<Long> hpExpire(String key, long millis, String... fields) {
return hpExpire(key, millis, Hash.FieldExpirationOptions.Condition.ALWAYS, fields);
return hpExpire(key, millis, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -2401,7 +2461,7 @@ public interface StringRedisConnection extends RedisConnection {
* @since 3.5
*/
@Nullable
List<Long> hpExpire(String key, long millis, Hash.FieldExpirationOptions.Condition condition, String... fields);
List<Long> hpExpire(String key, long millis, ExpirationOptions.Condition condition, String... fields);
/**
* Set the expiration for given {@code field} as a {@literal UNIX} timestamp.
@@ -2418,7 +2478,7 @@ public interface StringRedisConnection extends RedisConnection {
*/
@Nullable
default List<Long> hExpireAt(String key, long unixTime, String... fields) {
return hExpireAt(key, unixTime, Hash.FieldExpirationOptions.Condition.ALWAYS, fields);
return hExpireAt(key, unixTime, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -2436,7 +2496,7 @@ public interface StringRedisConnection extends RedisConnection {
* @since 3.5
*/
@Nullable
List<Long> hExpireAt(String key, long unixTime, Hash.FieldExpirationOptions.Condition condition, String... fields);
List<Long> hExpireAt(String key, long unixTime, ExpirationOptions.Condition condition, String... fields);
/**
* Set the expiration for given {@code field} as a {@literal UNIX} timestamp in milliseconds.
@@ -2453,7 +2513,7 @@ public interface StringRedisConnection extends RedisConnection {
*/
@Nullable
default List<Long> hpExpireAt(String key, long unixTimeInMillis, String... fields) {
return hpExpireAt(key, unixTimeInMillis, Hash.FieldExpirationOptions.Condition.ALWAYS, fields);
return hpExpireAt(key, unixTimeInMillis, ExpirationOptions.Condition.ALWAYS, fields);
}
/**
@@ -2471,7 +2531,7 @@ public interface StringRedisConnection extends RedisConnection {
* @since 3.5
*/
@Nullable
List<Long> hpExpireAt(String key, long unixTimeInMillis, Hash.FieldExpirationOptions.Condition condition,
List<Long> hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition,
String... fields);
/**

View File

@@ -27,7 +27,7 @@ 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.ExpirationOptions;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanCursor;
@@ -291,13 +291,13 @@ class JedisClusterHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(fields, "Fields must not be null");
try {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.getCluster().hexpire(key, seconds, fields);
}
@@ -308,13 +308,13 @@ class JedisClusterHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(fields, "Fields must not be null");
try {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.getCluster().hpexpire(key, millis, fields);
}
@@ -325,13 +325,13 @@ class JedisClusterHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(fields, "Fields must not be null");
try {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.getCluster().hexpireAt(key, unixTime, fields);
}
@@ -342,7 +342,7 @@ class JedisClusterHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields) {
Assert.notNull(key, "Key must not be null");
@@ -350,7 +350,7 @@ class JedisClusterHashCommands implements RedisHashCommands {
try {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.getCluster().hpexpireAt(key, unixTimeInMillis, fields);
}

View File

@@ -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.params.RestoreParams;
import redis.clients.jedis.params.ScanParams;
import redis.clients.jedis.resps.ScanResult;
@@ -35,6 +36,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisKeyCommands;
import org.springframework.data.redis.connection.RedisNode;
@@ -274,48 +276,67 @@ class JedisClusterKeyCommands implements RedisKeyCommands {
}
@Override
public Boolean expire(byte[] key, long seconds) {
public Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
try {
return JedisConverters.toBoolean(connection.getCluster().expire(key, seconds));
if (condition == ExpirationOptions.Condition.ALWAYS) {
return JedisConverters.toBoolean(connection.getCluster().expire(key, seconds));
}
return JedisConverters
.toBoolean(connection.getCluster().expire(key, seconds, ExpiryOption.valueOf(condition.name())));
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
@Override
public Boolean pExpire(byte[] key, long millis) {
public Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
try {
return JedisConverters.toBoolean(connection.getCluster().pexpire(key, millis));
if (condition == ExpirationOptions.Condition.ALWAYS) {
return JedisConverters.toBoolean(connection.getCluster().pexpire(key, millis));
}
return JedisConverters
.toBoolean(connection.getCluster().pexpire(key, millis, ExpiryOption.valueOf(condition.name())));
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
@Override
public Boolean expireAt(byte[] key, long unixTime) {
public Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
try {
return JedisConverters.toBoolean(connection.getCluster().expireAt(key, unixTime));
if (condition == ExpirationOptions.Condition.ALWAYS) {
return JedisConverters.toBoolean(connection.getCluster().expireAt(key, unixTime));
}
return JedisConverters
.toBoolean(connection.getCluster().expireAt(key, unixTime, ExpiryOption.valueOf(condition.name())));
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}
}
@Override
public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
public Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
try {
return JedisConverters.toBoolean(connection.getCluster().pexpireAt(key, unixTimeInMillis));
if (condition == ExpirationOptions.Condition.ALWAYS) {
return JedisConverters.toBoolean(connection.getCluster().pexpireAt(key, unixTimeInMillis));
}
return JedisConverters
.toBoolean(connection.getCluster().pexpireAt(key, unixTimeInMillis, ExpiryOption.valueOf(condition.name())));
} catch (Exception ex) {
throw convertJedisAccessException(ex);
}

View File

@@ -29,7 +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.ExpirationOptions;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
@@ -256,9 +256,9 @@ class JedisHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().just(Jedis::hexpire, PipelineBinaryCommands::hexpire, key, seconds, fields);
}
@@ -267,9 +267,9 @@ class JedisHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().just(Jedis::hpexpire, PipelineBinaryCommands::hpexpire, key, millis, fields);
}
@@ -278,9 +278,9 @@ class JedisHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().just(Jedis::hexpireAt, PipelineBinaryCommands::hexpireAt, key, unixTime, fields);
}
@@ -289,10 +289,10 @@ class JedisHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields) {
if (condition == FieldExpirationOptions.Condition.ALWAYS) {
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().just(Jedis::hpexpireAt, PipelineBinaryCommands::hpexpireAt, key, unixTimeInMillis,
fields);
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.connection.jedis;
import redis.clients.jedis.args.ExpiryOption;
import redis.clients.jedis.commands.JedisBinaryCommands;
import redis.clients.jedis.commands.PipelineBinaryCommands;
import redis.clients.jedis.params.RestoreParams;
@@ -30,6 +31,7 @@ import java.util.concurrent.TimeUnit;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisKeyCommands;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.ValueEncoding;
@@ -206,43 +208,69 @@ class JedisKeyCommands implements RedisKeyCommands {
}
@Override
public Boolean expire(byte[] key, long seconds) {
public Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
if (seconds > Integer.MAX_VALUE) {
return pExpire(key, TimeUnit.SECONDS.toMillis(seconds));
return pExpire(key, TimeUnit.SECONDS.toMillis(seconds), condition);
}
return connection.invoke().from(JedisBinaryCommands::expire, PipelineBinaryCommands::expire, key, seconds)
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().from(JedisBinaryCommands::expire, PipelineBinaryCommands::expire, key, seconds)
.get(JedisConverters.longToBoolean());
}
ExpiryOption option = ExpiryOption.valueOf(condition.name());
return connection.invoke().from(JedisBinaryCommands::expire, PipelineBinaryCommands::expire, key, seconds, option)
.get(JedisConverters.longToBoolean());
}
@Override
public Boolean pExpire(byte[] key, long millis) {
public Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().from(JedisBinaryCommands::pexpire, PipelineBinaryCommands::pexpire, key, millis)
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().from(JedisBinaryCommands::pexpire, PipelineBinaryCommands::pexpire, key, millis)
.get(JedisConverters.longToBoolean());
}
ExpiryOption option = ExpiryOption.valueOf(condition.name());
return connection.invoke().from(JedisBinaryCommands::pexpire, PipelineBinaryCommands::pexpire, key, millis, option)
.get(JedisConverters.longToBoolean());
}
@Override
public Boolean expireAt(byte[] key, long unixTime) {
public Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().from(JedisBinaryCommands::expireAt, PipelineBinaryCommands::expireAt, key, unixTime)
.get(JedisConverters.longToBoolean());
}
@Override
public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
Assert.notNull(key, "Key must not be null");
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke().from(JedisBinaryCommands::expireAt, PipelineBinaryCommands::expireAt, key, unixTime)
.get(JedisConverters.longToBoolean());
}
ExpiryOption option = ExpiryOption.valueOf(condition.name());
return connection.invoke()
.from(JedisBinaryCommands::pexpireAt, PipelineBinaryCommands::pexpireAt, key, unixTimeInMillis)
.from(JedisBinaryCommands::expireAt, PipelineBinaryCommands::expireAt, key, unixTime, option)
.get(JedisConverters.longToBoolean());
}
@Override
public Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
if (condition == ExpirationOptions.Condition.ALWAYS) {
return connection.invoke()
.from(JedisBinaryCommands::pexpireAt, PipelineBinaryCommands::pexpireAt, key, unixTimeInMillis)
.get(JedisConverters.longToBoolean());
}
ExpiryOption option = ExpiryOption.valueOf(condition.name());
return connection.invoke()
.from(JedisBinaryCommands::pexpireAt, PipelineBinaryCommands::pexpireAt, key, unixTimeInMillis, option)
.get(JedisConverters.longToBoolean());
}

View File

@@ -29,7 +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.ExpirationOptions;
import org.springframework.data.redis.connection.RedisHashCommands;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
@@ -215,25 +215,25 @@ class LettuceHashCommands implements RedisHashCommands {
}
@Override
public List<Long> hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) {
return connection.invoke().fromMany(RedisHashAsyncCommands::hexpire, key, seconds, getExpireArgs(condition), fields)
.toList();
}
@Override
public List<Long> hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) {
return connection.invoke().fromMany(RedisHashAsyncCommands::hpexpire, key, millis, getExpireArgs(condition), fields)
.toList();
}
@Override
public List<Long> hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) {
public List<Long> hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) {
return connection.invoke()
.fromMany(RedisHashAsyncCommands::hexpireat, key, unixTime, getExpireArgs(condition), fields).toList();
}
@Override
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition,
public List<Long> hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition,
byte[]... fields) {
return connection.invoke()
.fromMany(RedisHashAsyncCommands::hpexpireat, key, unixTimeInMillis, getExpireArgs(condition), fields).toList();
@@ -314,13 +314,13 @@ class LettuceHashCommands implements RedisHashCommands {
return value.hasValue() ? Converters.entryOf(value.getKey(), value.getValue()) : null;
}
private ExpireArgs getExpireArgs(FieldExpirationOptions.Condition condition) {
private static ExpireArgs getExpireArgs(ExpirationOptions.Condition condition) {
return new ExpireArgs() {
@Override
public <K, V> void build(CommandArgs<K, V> args) {
if (ObjectUtils.nullSafeEquals(condition, FieldExpirationOptions.Condition.ALWAYS)) {
if (ObjectUtils.nullSafeEquals(condition, ExpirationOptions.Condition.ALWAYS)) {
return;
}

View File

@@ -16,12 +16,14 @@
package org.springframework.data.redis.connection.lettuce;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
import io.lettuce.core.KeyScanCursor;
import io.lettuce.core.RestoreArgs;
import io.lettuce.core.ScanArgs;
import io.lettuce.core.ScanCursor;
import io.lettuce.core.SortArgs;
import io.lettuce.core.api.async.RedisKeyAsyncCommands;
import io.lettuce.core.protocol.CommandArgs;
import java.time.Duration;
import java.util.List;
@@ -30,6 +32,7 @@ import java.util.concurrent.TimeUnit;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisKeyCommands;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.connection.ValueEncoding;
@@ -39,6 +42,7 @@ import org.springframework.data.redis.core.Cursor;
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
@@ -192,35 +196,35 @@ class LettuceKeyCommands implements RedisKeyCommands {
}
@Override
public Boolean expire(byte[] key, long seconds) {
public Boolean expire(byte[] key, long seconds, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().just(RedisKeyAsyncCommands::expire, key, seconds);
return connection.invoke().just(RedisKeyAsyncCommands::expire, key, seconds, getExpireArgs(condition));
}
@Override
public Boolean pExpire(byte[] key, long millis) {
public Boolean pExpire(byte[] key, long millis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().just(RedisKeyAsyncCommands::pexpire, key, millis);
return connection.invoke().just(RedisKeyAsyncCommands::pexpire, key, millis, getExpireArgs(condition));
}
@Override
public Boolean expireAt(byte[] key, long unixTime) {
public Boolean expireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().just(RedisKeyAsyncCommands::expireat, key, unixTime);
return connection.invoke().just(RedisKeyAsyncCommands::expireat, key, unixTime, getExpireArgs(condition));
}
@Override
public Boolean pExpireAt(byte[] key, long unixTimeInMillis) {
public Boolean pExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition) {
Assert.notNull(key, "Key must not be null");
return connection.invoke().just(RedisKeyAsyncCommands::pexpireat, key, unixTimeInMillis);
return connection.invoke().just(RedisKeyAsyncCommands::pexpireat, key, unixTimeInMillis, getExpireArgs(condition));
}
@Override
@@ -337,4 +341,19 @@ class LettuceKeyCommands implements RedisKeyCommands {
return connection.invoke().just(RedisKeyAsyncCommands::objectRefcount, key);
}
private static ExpireArgs getExpireArgs(ExpirationOptions.Condition condition) {
return new ExpireArgs() {
@Override
public <K, V> void build(CommandArgs<K, V> args) {
if (ObjectUtils.nullSafeEquals(condition, ExpirationOptions.Condition.ALWAYS)) {
return;
}
args.add(condition.name());
}
};
}
}

View File

@@ -31,7 +31,7 @@ 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.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveHashCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
@@ -269,7 +269,8 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
}
@Override
public Flux<NumericResponse<ExpireCommand, Long>> applyExpiration(Publisher<ExpireCommand> commands) {
public Flux<NumericResponse<HashExpireCommand, Long>> applyHashFieldExpiration(
Publisher<HashExpireCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
@@ -287,7 +288,7 @@ class LettuceReactiveHashCommands implements ReactiveHashCommands {
@Override
public <K, V> void build(CommandArgs<K, V> args) {
super.build(args);
if (ObjectUtils.nullSafeEquals(command.getOptions(), FieldExpirationOptions.none())) {
if (ObjectUtils.nullSafeEquals(command.getOptions(), ExpirationOptions.none())) {
return;
}

View File

@@ -16,8 +16,10 @@
package org.springframework.data.redis.connection.lettuce;
import io.lettuce.core.CopyArgs;
import io.lettuce.core.ExpireArgs;
import io.lettuce.core.ScanStream;
import io.lettuce.core.api.reactive.RedisKeyReactiveCommands;
import io.lettuce.core.protocol.CommandArgs;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -25,9 +27,12 @@ import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveKeyCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection.BooleanResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
@@ -38,6 +43,7 @@ import org.springframework.data.redis.connection.ValueEncoding;
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
@@ -206,27 +212,45 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
}
@Override
public Flux<BooleanResponse<ExpireCommand>> expire(Publisher<ExpireCommand> commands) {
public Flux<BooleanResponse<ExpireCommand>> applyExpiration(Publisher<ExpireCommand> commands) {
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
Assert.notNull(command.getKey(), "Key must not be null");
Assert.notNull(command.getTimeout(), "Timeout must not be null");
return cmd.expire(command.getKey(), command.getTimeout().getSeconds())
.map(value -> new BooleanResponse<>(command, value));
}));
}
if (command.getExpiration().isPersistent()) {
return cmd.persist(command.getKey()).map(value -> new BooleanResponse<>(command, value));
}
@Override
public Flux<BooleanResponse<ExpireCommand>> pExpire(Publisher<ExpireCommand> commands) {
ExpireArgs args = new ExpireArgs() {
return connection.execute(cmd -> Flux.from(commands).concatMap(command -> {
@Override
public <K, V> void build(CommandArgs<K, V> args) {
super.build(args);
if (ObjectUtils.nullSafeEquals(command.getOptions(), ExpirationOptions.none())) {
return;
}
Assert.notNull(command.getKey(), "Key must not be null");
Assert.notNull(command.getTimeout(), "Timeout must not be null");
args.add(command.getOptions().getCondition().name());
}
};
return cmd.pexpire(command.getKey(), command.getTimeout().toMillis())
if (command.getExpiration().isUnixTimestamp()) {
if (command.getExpiration().getTimeUnit().equals(TimeUnit.MILLISECONDS)) {
return cmd.pexpireat(command.getKey(), command.getExpiration().getExpirationTimeInMilliseconds(), args)
.map(value -> new BooleanResponse<>(command, value));
}
return cmd.expireat(command.getKey(), command.getExpiration().getExpirationTimeInSeconds(), args)
.map(value -> new BooleanResponse<>(command, value));
}
if (command.getExpiration().getTimeUnit().equals(TimeUnit.MILLISECONDS)) {
return cmd.pexpire(command.getKey(), command.getExpiration().getExpirationTimeInMilliseconds(), args)
.map(value -> new BooleanResponse<>(command, value));
}
return cmd.expire(command.getKey(), command.getExpiration().getExpirationTimeInSeconds(), args)
.map(value -> new BooleanResponse<>(command, value));
}));
}
@@ -239,7 +263,7 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
Assert.notNull(command.getKey(), "Key must not be null");
Assert.notNull(command.getExpireAt(), "Expire at must not be null");
return cmd.expireat(command.getKey(), command.getExpireAt().getEpochSecond())
return cmd.expireat(command.getKey(), command.getExpireAt().getEpochSecond(), getExpireArgs(command.getOptions()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
@@ -252,7 +276,7 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
Assert.notNull(command.getKey(), "Key must not be null");
Assert.notNull(command.getExpireAt(), "Expire at must not be null");
return cmd.pexpireat(command.getKey(), command.getExpireAt().toEpochMilli())
return cmd.pexpireat(command.getKey(), command.getExpireAt().toEpochMilli(), getExpireArgs(command.getOptions()))
.map(value -> new BooleanResponse<>(command, value));
}));
}
@@ -319,4 +343,21 @@ class LettuceReactiveKeyCommands implements ReactiveKeyCommands {
public Mono<Long> refcount(ByteBuffer key) {
return connection.execute(cmd -> cmd.objectRefcount(key)).next();
}
private static ExpireArgs getExpireArgs(ExpirationOptions options) {
return new ExpireArgs() {
@Override
public <K, V> void build(CommandArgs<K, V> args) {
super.build(args);
if (ObjectUtils.nullSafeEquals(options.getCondition(), ExpirationOptions.Condition.ALWAYS)) {
return;
}
args.add(options.getCondition().name());
}
};
}
}

View File

@@ -19,7 +19,7 @@ import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.Hash;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.Expirations;
import org.springframework.lang.Nullable;
@@ -40,18 +40,17 @@ public interface BoundHashFieldExpirationOperations<HK> {
* @return changes to the hash fields. {@literal null} when used in pipeline / transaction.
*/
default ExpireChanges<HK> expire(Expiration expiration) {
return expire(expiration, Hash.FieldExpirationOptions.none());
return expire(expiration, ExpirationOptions.none());
}
/**
* Apply {@link Expiration} to the bound hash key/hash fields given {@link Hash.FieldExpirationOptions expiration
* options}.
* Apply {@link Expiration} to the bound hash key/hash fields given {@link ExpirationOptions expiration options}.
*
* @param expiration the expiration definition.
* @param options expiration options.
* @return changes to the hash fields. {@literal null} when used in pipeline / transaction.
*/
ExpireChanges<HK> expire(Expiration expiration, Hash.FieldExpirationOptions options);
ExpireChanges<HK> expire(Expiration expiration, ExpirationOptions options);
/**
* Set time to live for the bound hash key/hash fields.

View File

@@ -223,7 +223,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
default BoundHashFieldExpirationOperations<HK> expiration() {
default BoundHashFieldExpirationOperations<HK> hashExpiration() {
return new DefaultBoundHashFieldExpirationOperations<>(getOperations().opsForHash(), getKey(), this::keys);
}
@@ -235,8 +235,8 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
default BoundHashFieldExpirationOperations<HK> expiration(HK... hashFields) {
return expiration(Arrays.asList(hashFields));
default BoundHashFieldExpirationOperations<HK> hashExpiration(HK... hashFields) {
return hashExpiration(Arrays.asList(hashFields));
}
/**
@@ -247,7 +247,7 @@ public interface BoundHashOperations<H, HK, HV> extends BoundKeyOperations<H> {
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
default BoundHashFieldExpirationOperations<HK> expiration(Collection<HK> hashFields) {
default BoundHashFieldExpirationOperations<HK> hashExpiration(Collection<HK> hashFields) {
return new DefaultBoundHashFieldExpirationOperations<>(getOperations().opsForHash(), getKey(), () -> hashFields);
}

View File

@@ -21,7 +21,7 @@ import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.springframework.data.redis.connection.Hash;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.Expirations;
import org.springframework.lang.Nullable;
@@ -48,7 +48,7 @@ class DefaultBoundHashFieldExpirationOperations<H, HK> implements BoundHashField
}
@Override
public ExpireChanges<HK> expire(Expiration expiration, Hash.FieldExpirationOptions options) {
public ExpireChanges<HK> expire(Expiration expiration, ExpirationOptions options) {
return operations.expire(key, expiration, options, getHashKeys());
}

View File

@@ -27,7 +27,7 @@ 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.ExpirationOptions;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.Expirations;
@@ -251,13 +251,13 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
}
@Override
public ExpireChanges<HK> expire(K key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys) {
public ExpireChanges<HK> expire(K key, Expiration expiration, ExpirationOptions options, Collection<HK> hashKeys) {
List<HK> orderedKeys = List.copyOf(hashKeys);
byte[] rawKey = rawKey(key);
byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray());
List<Long> raw = execute(
connection -> connection.hashCommands().applyExpiration(rawKey, expiration, options, rawHashKeys));
connection -> connection.hashCommands().applyHashFieldExpiration(rawKey, expiration, options, rawHashKeys));
return raw != null ? ExpireChanges.of(orderedKeys, raw) : null;
}

View File

@@ -30,9 +30,9 @@ 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.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveHashCommands;
import org.springframework.data.redis.connection.ReactiveHashCommands.ExpireCommand;
import org.springframework.data.redis.connection.ReactiveHashCommands.HashExpireCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.types.Expiration;
@@ -244,11 +244,11 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
@Override
public Mono<ExpireChanges<HK>> expire(H key, Duration timeout, Collection<HK> hashKeys) {
return expire(key, Expiration.from(timeout), FieldExpirationOptions.none(), hashKeys);
return expire(key, Expiration.from(timeout), ExpirationOptions.none(), hashKeys);
}
@Override
public Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, FieldExpirationOptions options,
public Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, ExpirationOptions options,
Collection<HK> hashKeys) {
List<HK> orderedKeys = List.copyOf(hashKeys);
@@ -257,7 +257,8 @@ class DefaultReactiveHashOperations<H, HK, HV> implements ReactiveHashOperations
Mono<List<Long>> raw = createFlux(connection -> {
return connection
.applyExpiration(Mono.just(ExpireCommand.expire(rawHashKeys, expiration).from(rawKey).withOptions(options)))
.applyHashFieldExpiration(
Mono.just(HashExpireCommand.expire(rawHashKeys, expiration).from(rawKey).withOptions(options)))
.map(NumericResponse::getOutput);
}).collectList();

View File

@@ -151,6 +151,10 @@ public class ExpireChanges<K> {
public static final ExpiryChangeState OK = new ExpiryChangeState(1L);
public static final ExpiryChangeState EXPIRED = new ExpiryChangeState(2L);
static ExpiryChangeState of(boolean value) {
return value ? OK : CONDITION_NOT_MET;
}
static ExpiryChangeState of(Number value) {
return switch (value.intValue()) {
case -2 -> DOES_NOT_EXIST;

View File

@@ -24,7 +24,7 @@ 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.connection.ExpirationOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.Expirations;
import org.springframework.lang.Nullable;
@@ -258,7 +258,7 @@ public interface HashOperations<H, HK, HV> {
ExpireChanges<HK> expireAt(H key, Instant expireAt, Collection<HK> hashKeys);
/**
* Apply the expiration for given {@code hashKeys} as a {@literal date} timestamp.
* Apply the expiration for given {@code hashKeys}.
*
* @param key must not be {@literal null}.
* @param expiration must not be {@literal null}.
@@ -266,11 +266,15 @@ public interface HashOperations<H, HK, HV> {
* @param hashKeys must not be {@literal null}.
* @return changes to the hash fields. {@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>
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HEXPIRE</a>
* @see <a href="https://redis.io/docs/latest/commands/hpexpire/">Redis Documentation: HPEXPIRE</a>
* @see <a href="https://redis.io/docs/latest/commands/hexpireat/">Redis Documentation: HEXPIREAT</a>
* @see <a href="https://redis.io/docs/latest/commands/hpexpireat/">Redis Documentation: HPEXPIREAT</a>
* @see <a href="https://redis.io/docs/latest/commands/hpersist/">Redis Documentation: HPERSIST</a>
* @since 3.5
*/
@Nullable
ExpireChanges<HK> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys);
ExpireChanges<HK> expire(H key, Expiration expiration, ExpirationOptions options, Collection<HK> hashKeys);
/**
* Remove the expiration from given {@code hashKeys} .

View File

@@ -26,7 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.Hash.FieldExpirationOptions;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.Expirations;
import org.springframework.lang.Nullable;
@@ -263,7 +263,7 @@ public interface ReactiveHashOperations<H, HK, HV> {
* @see <a href="https://redis.io/docs/latest/commands/hexpire/">Redis Documentation: HEXPIRE</a>
* @since 3.5
*/
Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection<HK> hashKeys);
Mono<ExpireChanges<HK>> expire(H key, Expiration expiration, ExpirationOptions options, Collection<HK> hashKeys);
/**
* Set the expiration for given {@code hashKey} as a {@literal date} timestamp.

View File

@@ -26,9 +26,12 @@ import java.util.Collections;
import java.util.List;
import org.reactivestreams.Publisher;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveSubscription.Message;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.hash.HashMapper;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.PatternTopic;
@@ -373,6 +376,22 @@ public interface ReactiveRedisOperations<K, V> {
*/
Mono<Boolean> expireAt(K key, Instant expireAt);
/**
* Set the expiration for given {@code key}.
*
* @param key must not be {@literal null}.
* @param expiration must not be {@literal null}.
* @param options must not be {@literal null}.
* @throws IllegalArgumentException if the instant is {@literal null} or too large to represent as a {@code Date}.
* @see <a href="https://redis.io/commands/expire">Redis Documentation: EXPIRE</a>
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see <a href="https://redis.io/commands/persist">Redis Documentation: PERSIST</a>
* @since 3.5
*/
Mono<ExpireChanges.ExpiryChangeState> expire(K key, Expiration expiration, ExpirationOptions options);
/**
* Remove the expiration from given {@code key}.
*

View File

@@ -29,6 +29,8 @@ import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveKeyCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
@@ -36,6 +38,7 @@ import org.springframework.data.redis.connection.ReactiveSubscription.Message;
import org.springframework.data.redis.core.script.DefaultReactiveScriptExecutor;
import org.springframework.data.redis.core.script.ReactiveScriptExecutor;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.hash.HashMapper;
import org.springframework.data.redis.hash.ObjectHashMapper;
import org.springframework.data.redis.listener.ReactiveRedisMessageListenerContainer;
@@ -454,6 +457,19 @@ public class ReactiveRedisTemplate<K, V> implements ReactiveRedisOperations<K, V
return doCreateMono(connection -> connection.keyCommands().pExpireAt(rawKey(key), expireAt));
}
@Override
public Mono<ExpireChanges.ExpiryChangeState> expire(K key, Expiration expiration, ExpirationOptions options) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(expiration, "Expiration at must not be null");
Assert.notNull(options, "ExpirationOptions at must not be null");
Mono<ReactiveKeyCommands.ExpireCommand> just = Mono
.just(ReactiveKeyCommands.ExpireCommand.expire(rawKey(key), expiration).withOptions(options));
return doCreateMono(connection -> connection.keyCommands().applyExpiration(just))
.map(ReactiveRedisConnection.BooleanResponse::getOutput).map(ExpireChanges.ExpiryChangeState::of);
}
@Override
public Mono<Boolean> persist(K key) {

View File

@@ -77,8 +77,8 @@ public enum RedisCommand {
EVALSHA("rw", 2), //
EXEC("rw", 0, 0), //
EXISTS("r", 1, 1), //
EXPIRE("rw", 2, 2), //
EXPIREAT("rw", 2, 2), //
EXPIRE("rw", 2), //
EXPIREAT("rw", 2), //
// -- F
FLUSHALL("w", 0, 0), //
FLUSHDB("w", 0, 0), //
@@ -142,8 +142,8 @@ public enum RedisCommand {
MULTI("rw", 0, 0), //
// -- P
PERSIST("rw", 1, 1), //
PEXPIRE("rw", 2, 2), //
PEXPIREAT("rw", 2, 2), //
PEXPIRE("rw", 2), //
PEXPIREAT("rw", 2), //
PING("r", 0, 0), //
PSETEX("w", 3), //
PSUBSCRIBE("r", 1), //

View File

@@ -25,10 +25,12 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.hash.HashMapper;
import org.springframework.data.redis.serializer.RedisSerializer;
@@ -366,6 +368,24 @@ public interface RedisOperations<K, V> {
return expireAt(key, Date.from(expireAt));
}
/**
* Set the expiration for given {@code key}.
*
* @param key must not be {@literal null}.
* @param expiration must not be {@literal null}.
* @param options must not be {@literal null}.
* @return changes to the expiry. {@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/commands/expire">Redis Documentation: EXPIRE</a>
* @see <a href="https://redis.io/commands/pexpire">Redis Documentation: PEXPIRE</a>
* @see <a href="https://redis.io/commands/expireat">Redis Documentation: EXPIREAT</a>
* @see <a href="https://redis.io/commands/pexpireat">Redis Documentation: PEXPIREAT</a>
* @see <a href="https://redis.io/commands/persist">Redis Documentation: PERSIST</a>
* @since 3.5
*/
@Nullable
ExpireChanges.ExpiryChangeState expire(K key, Expiration expiration, ExpirationOptions options);
/**
* Remove the expiration from given {@code key}.
*

View File

@@ -33,6 +33,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisKeyCommands;
@@ -46,6 +47,7 @@ import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.DefaultScriptExecutor;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.script.ScriptExecutor;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.hash.HashMapper;
import org.springframework.data.redis.hash.ObjectHashMapper;
@@ -711,6 +713,16 @@ public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperation
});
}
@Nullable
@Override
public ExpireChanges.ExpiryChangeState expire(K key, Expiration expiration, ExpirationOptions options) {
byte[] rawKey = rawKey(key);
Boolean raw = doWithKeys(connection -> connection.applyExpiration(rawKey, expiration, options));
return raw != null ? ExpireChanges.ExpiryChangeState.of(raw) : null;
}
@Override
public Boolean persist(K key) {

View File

@@ -324,13 +324,13 @@ public class DefaultRedisMap<K, V> implements RedisMap<K, V> {
}
@Override
public BoundHashFieldExpirationOperations<K> expiration() {
return hashOps.expiration();
public BoundHashFieldExpirationOperations<K> hashFieldExpiration() {
return hashOps.hashExpiration();
}
@Override
public BoundHashFieldExpirationOperations<K> expiration(Collection<K> hashFields) {
return hashOps.expiration(hashFields);
public BoundHashFieldExpirationOperations<K> hashFieldExpiration(Collection<K> hashFields) {
return hashOps.hashExpiration(hashFields);
}
private void checkResult(@Nullable Object obj) {

View File

@@ -78,34 +78,35 @@ public interface RedisMap<K, V> extends RedisStore, ConcurrentMap<K, V> {
Iterator<Map.Entry<K, V>> scan();
/**
* Returns a bound operations object to perform operations on the hash field expiration for all hash fields at
* {@code key}. Operations on the expiration object obtain keys at the time of invoking any expiration operation.
* Returns a bound operations object to perform operations on the hash field expiration for all hash fields at the
* bound {@link #getKey()}. Operations on the expiration object obtain keys at the time of invoking any expiration
* operation.
*
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
BoundHashFieldExpirationOperations<K> expiration();
BoundHashFieldExpirationOperations<K> hashFieldExpiration();
/**
* Returns a bound operations object to perform operations on the hash field expiration for all hash fields at the
* bound {@code key} for the given hash fields.
* bound {@link #getKey()} for the given hash fields.
*
* @param hashFields collection of hash fields to operate on.
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
default BoundHashFieldExpirationOperations<K> expiration(K... hashFields) {
return expiration(Arrays.asList(hashFields));
default BoundHashFieldExpirationOperations<K> hashFieldExpiration(K... hashFields) {
return hashFieldExpiration(Arrays.asList(hashFields));
}
/**
* Returns a bound operations object to perform operations on the hash field expiration for all hash fields at the
* bound {@code key} for the given hash fields.
* bound {@link #getKey()} for the given hash fields.
*
* @param hashFields collection of hash fields to operate on.
* @return the bound operations object to perform operations on the hash field expiration.
* @since 3.5
*/
BoundHashFieldExpirationOperations<K> expiration(Collection<K> hashFields);
BoundHashFieldExpirationOperations<K> hashFieldExpiration(Collection<K> hashFields);
}

View File

@@ -307,13 +307,13 @@ public class RedisProperties extends Properties implements RedisMap<Object, Obje
}
@Override
public BoundHashFieldExpirationOperations<Object> expiration() {
return (BoundHashFieldExpirationOperations) delegate.expiration();
public BoundHashFieldExpirationOperations<Object> hashFieldExpiration() {
return (BoundHashFieldExpirationOperations) delegate.hashFieldExpiration();
}
@Override
public BoundHashFieldExpirationOperations<Object> expiration(Collection<Object> hashFields) {
return (BoundHashFieldExpirationOperations) delegate.expiration((Collection) hashFields);
public BoundHashFieldExpirationOperations<Object> hashFieldExpiration(Collection<Object> hashFields) {
return (BoundHashFieldExpirationOperations) delegate.hashFieldExpiration((Collection) hashFields);
}
}

View File

@@ -15,48 +15,24 @@
*/
package org.springframework.data.redis.connection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.within;
import static org.assertj.core.api.Assumptions.assumeThat;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.condition.OS.MAC;
import static org.springframework.data.redis.connection.BitFieldSubCommands.create;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.FAIL;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.INT_8;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.signed;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.unsigned;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_4;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.KILOMETERS;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.newGeoSearchArgs;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoSearchStoreCommandArgs.newGeoSearchStoreArgs;
import static org.springframework.data.redis.core.ScanOptions.scanOptions;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import static org.awaitility.Awaitility.*;
import static org.junit.jupiter.api.condition.OS.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoSearchStoreCommandArgs.*;
import static org.springframework.data.redis.core.ScanOptions.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
@@ -71,6 +47,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Range;
@@ -215,6 +192,23 @@ public abstract class AbstractConnectionIntegrationTests {
await().atMost(Duration.ofMillis(3000L)).until(keyExpired::passes);
}
@LongRunningTest // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
void testExpireWithArgs() {
actual.add(connection.set("exp", "true"));
actual.add(
connection.applyExpiration("exp".getBytes(), Expiration.from(Duration.ofMinutes(1)), ExpirationOptions.none()));
actual.add(connection.applyExpiration("exp".getBytes(), Expiration.from(Duration.ofMinutes(1)),
ExpirationOptions.builder().nx().build()));
actual.add(connection.applyExpiration("exp".getBytes(), Expiration.from(Duration.ofMinutes(2)),
ExpirationOptions.builder().gt().build()));
actual.add(connection.applyExpiration("exp".getBytes(), Expiration.from(Duration.ofMinutes(3)),
ExpirationOptions.builder().lt().build()));
verifyResults(Arrays.asList(true, true, false, true, false));
}
@Test // DATAREDIS-1103
void testSetWithKeepTTL() {
@@ -777,7 +771,25 @@ public abstract class AbstractConnectionIntegrationTests {
assertThat(stringSerializer.deserialize((byte[]) getResults().get(1))).isEqualTo("bar");
}
@Test // GH-
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
void testExecuteExpirationWithConditions() {
actual.add(connection.set("foo", "bar"));
actual.add(connection.execute("TTL", "foo"));
actual.add(connection.execute("EXPIRE", "foo", "100", "NX"));
actual.add(connection.execute("PERSIST", "foo"));
actual.add(connection.execute("TTL", "foo"));
List<Object> results = getResults();
assertThat(results.get(1)).isEqualTo(-1L);
assertThat(results.get(2)).isIn(1L, true);
assertThat(results.get(3)).isIn(1L, true);
assertThat(results.get(4)).isEqualTo(-1L);
}
@Test // GH-3054
@EnabledOnCommand("HEXPIRE")
void testExecuteHashFieldExpiration() {

View File

@@ -119,7 +119,7 @@ public interface ClusterConnectionTests {
void expireAtShouldBeSetCorrectly();
// DATAREDIS-315
void expireShouldBeSetCorreclty();
void expireShouldBeSetCorrectly();
// DATAREDIS-315
void flushDbOnSingleNodeShouldFlushOnlyGivenNodesDb();
@@ -386,7 +386,7 @@ public interface ClusterConnectionTests {
void pExpireAtShouldBeSetCorrectly();
// DATAREDIS-315
void pExpireShouldBeSetCorreclty();
void pExpireShouldBeSetCorrectly();
// DATAREDIS-315
void pSetExShouldSetValueCorrectly();

View File

@@ -273,28 +273,28 @@ public class DefaultStringRedisConnectionTests {
@Test
public void testExpireBytes() {
doReturn(true).when(nativeConnection).expire(fooBytes, 1L);
doReturn(true).when(nativeConnection).expire(fooBytes, 1L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.expire(fooBytes, 1L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testExpire() {
doReturn(true).when(nativeConnection).expire(fooBytes, 1L);
doReturn(true).when(nativeConnection).expire(fooBytes, 1L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.expire(foo, 1L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testExpireAtBytes() {
doReturn(true).when(nativeConnection).expireAt(fooBytes, 1L);
doReturn(true).when(nativeConnection).expireAt(fooBytes, 1L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.expireAt(fooBytes, 1L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testExpireAt() {
doReturn(true).when(nativeConnection).expireAt(fooBytes, 1L);
doReturn(true).when(nativeConnection).expireAt(fooBytes, 1L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.expireAt(foo, 1L));
verifyResults(Collections.singletonList(true));
}
@@ -1662,28 +1662,28 @@ public class DefaultStringRedisConnectionTests {
@Test
public void testPExpireBytes() {
doReturn(true).when(nativeConnection).pExpire(fooBytes, 34L);
doReturn(true).when(nativeConnection).pExpire(fooBytes, 34L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.pExpire(fooBytes, 34L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testPExpire() {
doReturn(true).when(nativeConnection).pExpire(fooBytes, 34L);
doReturn(true).when(nativeConnection).pExpire(fooBytes, 34L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.pExpire(foo, 34L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testPExpireAtBytes() {
doReturn(true).when(nativeConnection).pExpireAt(fooBytes, 34L);
doReturn(true).when(nativeConnection).pExpireAt(fooBytes, 34L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.pExpireAt(fooBytes, 34L));
verifyResults(Collections.singletonList(true));
}
@Test
public void testPExpireAt() {
doReturn(true).when(nativeConnection).pExpireAt(fooBytes, 34L);
doReturn(true).when(nativeConnection).pExpireAt(fooBytes, 34L, ExpirationOptions.Condition.ALWAYS);
actual.add(connection.pExpireAt(foo, 34L));
verifyResults(Collections.singletonList(true));
}

View File

@@ -15,37 +15,18 @@
*/
package org.springframework.data.redis.connection.jedis;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.data.Offset.*;
import static org.assertj.core.data.Offset.offset;
import static org.springframework.data.redis.connection.BitFieldSubCommands.create;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.FAIL;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.INT_8;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.signed;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.unsigned;
import static org.springframework.data.redis.connection.ClusterTestVariables.CLUSTER_HOST;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_1_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_2_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_3_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.REPLICAOF_NODE_1_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_3;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.KILOMETERS;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs;
import static org.springframework.data.redis.connection.RedisListCommands.Direction;
import static org.springframework.data.redis.connection.RedisListCommands.Position;
import static org.springframework.data.redis.connection.RedisZSetCommands.Range;
import static org.springframework.data.redis.core.ScanOptions.NONE;
import static org.springframework.data.redis.core.ScanOptions.scanOptions;
import static org.springframework.data.redis.connection.BitFieldSubCommands.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
import static org.springframework.data.redis.connection.RedisListCommands.*;
import static org.springframework.data.redis.connection.RedisZSetCommands.*;
import static org.springframework.data.redis.core.ScanOptions.*;
import redis.clients.jedis.ConnectionPool;
import redis.clients.jedis.HostAndPort;
@@ -57,23 +38,14 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Range.Bound;
@@ -81,21 +53,13 @@ import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.ClusterConnectionTests;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ClusterTopology;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.DefaultSortParameters;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.Limit;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisServerCommands.FlushOption;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
import org.springframework.data.redis.connection.zset.DefaultTuple;
import org.springframework.data.redis.connection.zset.Tuple;
@@ -472,8 +436,24 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void expireAtWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 5000,
ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 5000,
ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 15000,
ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void expireShouldBeSetCorreclty() {
public void expireShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
@@ -482,6 +462,19 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void expireWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void flushDbOnSingleNodeShouldFlushOnlyGivenNodesDb() {
@@ -1597,8 +1590,27 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void pExpireAtWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 5000, ExpirationOptions.Condition.XX))
.isFalse();
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 5000, ExpirationOptions.Condition.NX))
.isTrue();
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 15000, ExpirationOptions.Condition.LT))
.isFalse();
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void pExpireShouldBeSetCorreclty() {
public void pExpireShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
@@ -1607,6 +1619,19 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void pExpireWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(JedisConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void pSetExShouldSetValueCorrectly() {

View File

@@ -15,35 +15,17 @@
*/
package org.springframework.data.redis.connection.lettuce;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.data.Offset.*;
import static org.assertj.core.data.Offset.offset;
import static org.springframework.data.redis.connection.BitFieldSubCommands.create;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.FAIL;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.INT_8;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.signed;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.unsigned;
import static org.springframework.data.redis.connection.ClusterTestVariables.CLUSTER_HOST;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.KEY_4;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_1_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_2_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.MASTER_NODE_3_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.REPLICAOF_NODE_1_PORT;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.SAME_SLOT_KEY_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_1;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_2;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_3;
import static org.springframework.data.redis.connection.ClusterTestVariables.VALUE_4;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.KILOMETERS;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs;
import static org.springframework.data.redis.connection.RedisZSetCommands.Range;
import static org.springframework.data.redis.core.ScanOptions.scanOptions;
import static org.springframework.data.redis.connection.BitFieldSubCommands.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldIncrBy.Overflow.*;
import static org.springframework.data.redis.connection.BitFieldSubCommands.BitFieldType.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.DistanceUnit.*;
import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs.*;
import static org.springframework.data.redis.connection.RedisZSetCommands.*;
import static org.springframework.data.redis.core.ScanOptions.*;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
@@ -52,17 +34,7 @@ import io.lettuce.core.codec.ByteArrayCodec;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.assertj.core.data.Offset;
@@ -71,6 +43,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Range.Bound;
@@ -78,21 +51,11 @@ import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.ClusterConnectionTests;
import org.springframework.data.redis.connection.ClusterSlotHashUtil;
import org.springframework.data.redis.connection.ClusterTestVariables;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.DefaultSortParameters;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.Limit;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisServerCommands.FlushOption;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
@@ -538,8 +501,24 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void expireAtWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 5000,
ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 5000,
ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.expireAt(KEY_1_BYTES, System.currentTimeMillis() / 1000 + 10000,
ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void expireShouldBeSetCorreclty() {
public void expireShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
@@ -548,6 +527,19 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void expireWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.expire(KEY_1_BYTES, 15, ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void flushDbOnSingleNodeShouldFlushOnlyGivenNodesDb() {
@@ -1666,8 +1658,27 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void pExpireAtWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 5000, ExpirationOptions.Condition.XX))
.isFalse();
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 5000, ExpirationOptions.Condition.NX))
.isTrue();
assertThat(
clusterConnection.pExpireAt(KEY_1_BYTES, System.currentTimeMillis() + 15000, ExpirationOptions.Condition.LT))
.isFalse();
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void pExpireShouldBeSetCorreclty() {
public void pExpireShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
@@ -1676,6 +1687,19 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
public void pExpireWithConditionShouldBeSetCorrectly() {
nativeConnection.set(KEY_1, VALUE_1);
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.XX)).isFalse();
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.NX)).isTrue();
assertThat(clusterConnection.pExpire(KEY_1_BYTES, 15000, ExpirationOptions.Condition.LT)).isFalse();
assertThat(nativeConnection.ttl(LettuceConverters.toString(KEY_1_BYTES))).isGreaterThan(1);
}
@Test // DATAREDIS-315
public void pSetExShouldSetValueCorrectly() {

View File

@@ -32,11 +32,15 @@ import java.util.List;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveKeyCommands;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
import org.springframework.data.redis.connection.ReactiveRedisConnection.KeyCommand;
import org.springframework.data.redis.connection.ReactiveRedisConnection.NumericResponse;
import org.springframework.data.redis.connection.ValueEncoding.RedisValueEncoding;
import org.springframework.data.redis.core.KeyScanOptions;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.test.condition.EnabledOnCommand;
import org.springframework.data.redis.test.condition.EnabledOnRedisVersion;
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
@@ -303,6 +307,35 @@ public class LettuceReactiveKeyCommandsIntegrationTests extends LettuceReactiveC
assertThat(nativeCommands.ttl(KEY_1)).isGreaterThan(8L);
}
@ParameterizedRedisTest // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
void shouldExpireWithOptionsKeysCorrectly() {
nativeCommands.set(KEY_1, VALUE_1);
connection.keyCommands()
.applyExpiration(
Mono.just(ReactiveKeyCommands.ExpireCommand.expire(KEY_1_BBUFFER, Expiration.from(Duration.ofSeconds(10)))
.withOptions(ExpirationOptions.builder().xx().build())))
.map(ReactiveRedisConnection.BooleanResponse::getOutput).as(StepVerifier::create) //
.expectNext(false) //
.expectComplete() //
.verify();
assertThat(nativeCommands.ttl(KEY_1)).isEqualTo(-1L);
connection.keyCommands()
.applyExpiration(
Mono.just(ReactiveKeyCommands.ExpireCommand.expire(KEY_1_BBUFFER, Expiration.from(Duration.ofSeconds(10)))
.withOptions(ExpirationOptions.builder().nx().build())))
.map(ReactiveRedisConnection.BooleanResponse::getOutput).as(StepVerifier::create) //
.expectNext(true) //
.expectComplete() //
.verify();
assertThat(nativeCommands.ttl(KEY_1)).isGreaterThan(8L);
}
@ParameterizedRedisTest // DATAREDIS-602, DATAREDIS-1031
void shouldPreciseExpireKeysCorrectly() {

View File

@@ -15,9 +15,8 @@
*/
package org.springframework.data.redis.core;
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 static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import java.io.IOException;
import java.time.Duration;
@@ -30,10 +29,11 @@ import java.util.concurrent.TimeUnit;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.BeforeEach;
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.ExpirationOptions;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.extension.JedisConnectionFactoryExtension;
import org.springframework.data.redis.core.ExpireChanges.ExpiryChangeState;
@@ -281,7 +281,7 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
hashOps.put(key, key2, val2);
BoundHashOperations<K, HK, HV> hashOps = redisTemplate.boundHashOps(key);
BoundHashFieldExpirationOperations<HK> exp = hashOps.expiration(key1, key2);
BoundHashFieldExpirationOperations<HK> exp = hashOps.hashExpiration(key1, key2);
assertThat(exp.expire(Duration.ofSeconds(5))).satisfies(changes -> {
assertThat(changes.allOk()).isTrue();
@@ -350,7 +350,7 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
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));
org.springframework.data.redis.core.types.Expiration.seconds(20), ExpirationOptions.none(), List.of(key1));
assertThat(expire.allOk()).isTrue();
}
@@ -369,12 +369,12 @@ public class DefaultHashOperationsIntegrationTests<K, HK, HV> {
hashOps.put(key, key2, val2);
redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20),
FieldExpirationOptions.none(), List.of(key1));
ExpirationOptions.none(), List.of(key1));
redisTemplate.opsForHash().expire(key, org.springframework.data.redis.core.types.Expiration.seconds(60),
FieldExpirationOptions.none(), List.of(key2));
ExpirationOptions.none(), List.of(key2));
ExpireChanges<Object> changes = redisTemplate.opsForHash().expire(key,
org.springframework.data.redis.core.types.Expiration.seconds(30), FieldExpirationOptions.builder().gt().build(),
org.springframework.data.redis.core.types.Expiration.seconds(30), ExpirationOptions.builder().gt().build(),
List.of(key1, key2));
assertThat(changes.ok()).containsExactly(key1);

View File

@@ -15,9 +15,9 @@
*/
package org.springframework.data.redis.core;
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 static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import static org.junit.jupiter.api.condition.OS.*;
import reactor.test.StepVerifier;
@@ -33,11 +33,12 @@ 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.Hash.FieldExpirationOptions;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.convert.Converters;
@@ -543,14 +544,14 @@ public class DefaultReactiveHashOperationsIntegrationTests<K, HK, HV> {
putAll(key, key1, val1, key2, val2);
hashOperations
.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20), FieldExpirationOptions.none(),
.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(20), ExpirationOptions.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(),
.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(60), ExpirationOptions.none(),
List.of(key2))
.as(StepVerifier::create)//
.assertNext(changes -> {
@@ -559,7 +560,7 @@ public class DefaultReactiveHashOperationsIntegrationTests<K, HK, HV> {
hashOperations
.expire(key, org.springframework.data.redis.core.types.Expiration.seconds(30),
FieldExpirationOptions.builder().gt().build(), List.of(key1, key2))
ExpirationOptions.builder().gt().build(), List.of(key1, key2))
.as(StepVerifier::create)//
.assertNext(changes -> {
assertThat(changes.ok()).containsExactly(key1);

View File

@@ -37,6 +37,7 @@ import org.springframework.data.redis.Person;
import org.springframework.data.redis.PersonObjectFactory;
import org.springframework.data.redis.StringObjectFactory;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.ExpirationOptions;
import org.springframework.data.redis.connection.ReactiveRedisClusterConnection;
import org.springframework.data.redis.connection.ReactiveSubscription.ChannelMessage;
import org.springframework.data.redis.connection.ReactiveSubscription.Message;
@@ -45,6 +46,7 @@ import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveOperationsTestParams.Fixture;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisElementReader;
@@ -323,6 +325,24 @@ public class ReactiveRedisTemplateIntegrationTests<K, V> {
.consumeNextWith(actual -> assertThat(actual).isGreaterThan(Duration.ofSeconds(8))).verifyComplete();
}
@ParameterizedRedisTest // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
void expireWithCondition() {
K key = keyFactory.instance();
V value = valueFactory.instance();
redisTemplate.opsForValue().set(key, value).as(StepVerifier::create).expectNext(true).verifyComplete();
redisTemplate.expire(key, Expiration.seconds(10), ExpirationOptions.none()).as(StepVerifier::create)
.expectNext(ExpireChanges.ExpiryChangeState.OK).verifyComplete();
redisTemplate.expire(key, Expiration.seconds(20), ExpirationOptions.builder().lt().build()).as(StepVerifier::create)
.expectNext(ExpireChanges.ExpiryChangeState.CONDITION_NOT_MET).verifyComplete();
redisTemplate.getExpire(key).as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual).isGreaterThan(Duration.ofSeconds(5))).verifyComplete();
}
@ParameterizedRedisTest // DATAREDIS-602
void preciseExpire() {
@@ -337,6 +357,24 @@ public class ReactiveRedisTemplateIntegrationTests<K, V> {
.consumeNextWith(actual -> assertThat(actual).isGreaterThan(Duration.ofSeconds(8))).verifyComplete();
}
@ParameterizedRedisTest // GH-3114
@EnabledOnCommand("SPUBLISH") // Redis 7.0
void preciseExpireWithCondition() {
K key = keyFactory.instance();
V value = valueFactory.instance();
redisTemplate.opsForValue().set(key, value).as(StepVerifier::create).expectNext(true).verifyComplete();
redisTemplate.expire(key, Expiration.milliseconds(10000), ExpirationOptions.none()).as(StepVerifier::create)
.expectNext(ExpireChanges.ExpiryChangeState.OK).verifyComplete();
redisTemplate.expire(key, Expiration.milliseconds(20000), ExpirationOptions.builder().lt().build())
.as(StepVerifier::create).expectNext(ExpireChanges.ExpiryChangeState.CONDITION_NOT_MET).verifyComplete();
redisTemplate.getExpire(key).as(StepVerifier::create) //
.consumeNextWith(actual -> assertThat(actual).isGreaterThan(Duration.ofSeconds(5))).verifyComplete();
}
@ParameterizedRedisTest // DATAREDIS-602
void expireAt() {

View File

@@ -15,9 +15,8 @@
*/
package org.springframework.data.redis.support.collections;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assumptions.assumeThat;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.*;
import static org.assertj.core.api.Assumptions.*;
import java.io.IOException;
import java.text.DecimalFormat;
@@ -36,6 +35,7 @@ import java.util.concurrent.TimeUnit;
import org.assertj.core.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.DoubleAsStringObjectFactory;
import org.springframework.data.redis.LongAsStringObjectFactory;
@@ -205,7 +205,7 @@ public abstract class AbstractRedisMapIntegrationTests<K, V> {
V v1 = getValue();
assertThat(map.put(k1, v1)).isEqualTo(null);
BoundHashFieldExpirationOperations<K> ops = map.expiration(Collections.singletonList(k1));
BoundHashFieldExpirationOperations<K> ops = map.hashFieldExpiration(Collections.singletonList(k1));
assertThat(ops.expire(Duration.ofSeconds(5))).satisfies(ExpireChanges::allOk);
assertThat(ops.getTimeToLive()).satisfies(expiration -> {
assertThat(expiration.expirationOf(k1).raw()).isBetween(1L, 5L);
@@ -224,7 +224,7 @@ public abstract class AbstractRedisMapIntegrationTests<K, V> {
V v1 = getValue();
assertThat(map.put(k1, v1)).isEqualTo(null);
BoundHashFieldExpirationOperations<K> ops = map.expiration(Collections.singletonList(k1));
BoundHashFieldExpirationOperations<K> ops = map.hashFieldExpiration(Collections.singletonList(k1));
assertThat(ops.expireAt(Instant.now().plusSeconds(5))).satisfies(ExpireChanges::allOk);
assertThat(ops.getTimeToLive()).satisfies(expiration -> {
assertThat(expiration.expirationOf(k1).raw()).isBetween(1L, 5L);