diff --git a/pom.xml b/pom.xml index fd73e23ebd..afbe1266d1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 3.5.0-SNAPSHOT + 3.5.0-GH-3114-SNAPSHOT Spring Data Redis Spring Data module for Redis diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 831a46ece2..c2ea198d8b 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -31,7 +31,6 @@ 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 Long exists(byte[]... keys) { } @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 Long zLexCount(String key, org.springframework.data.domain.Range } @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 Boolean exists(String key) { } @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 Object execute(String command, String... args) { } @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 Long hStrLen(byte[] key, byte[] field) { return convertAndReturn(delegate.hStrLen(key, field), Converters.identityConverter()); } - public @Nullable List applyExpiration(byte[] key, + public @Nullable List 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 hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) { return this.delegate.hExpire(key, seconds, condition, fields); } @Override - public List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) { return this.delegate.hpExpire(key, millis, condition, fields); } @Override - public List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) { return this.delegate.hExpireAt(key, unixTime, condition, fields); } @Override - public List hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + public List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition, byte[]... fields) { return this.delegate.hpExpireAt(key, unixTimeInMillis, condition, fields); } @@ -2630,27 +2629,27 @@ public List hTtl(byte[] key, TimeUnit timeUnit, byte[]... fields) { public @Nullable List 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 hExpire(String key, long seconds, FieldExpirationOptions.Condition condition, String... fields) { + public List hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields) { return hExpire(serialize(key), seconds, condition, serializeMulti(fields)); } @Override - public List hpExpire(String key, long millis, FieldExpirationOptions.Condition condition, String... fields) { + public List hpExpire(String key, long millis, ExpirationOptions.Condition condition, String... fields) { return hpExpire(serialize(key), millis, condition, serializeMulti(fields)); } @Override - public List hExpireAt(String key, long unixTime, FieldExpirationOptions.Condition condition, String... fields) { + public List hExpireAt(String key, long unixTime, ExpirationOptions.Condition condition, String... fields) { return hExpireAt(serialize(key), unixTime, condition, serializeMulti(fields)); } @Override - public List hpExpireAt(String key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + public List hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition, String... fields) { return hpExpireAt(serialize(key), unixTimeInMillis, condition, serializeMulti(fields)); } diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index 8190caa6a0..f3be7ab276 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -28,7 +28,6 @@ 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 @@ default Boolean renameNX(byte[] sourceKey, byte[] targetKey) { @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 @@ default Long pTtl(byte[] key, TimeUnit timeUnit) { @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 @@ default Long hStrLen(byte[] key, byte[] field) { @Override @Deprecated default List 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 hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) { + default List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields) { return hashCommands().hExpire(key, seconds, condition, fields); } @@ -1497,13 +1524,13 @@ default List hExpire(byte[] key, long seconds, FieldExpirationOptions.Cond @Override @Deprecated default List 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 hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) { + default List hpExpire(byte[] key, long millis, ExpirationOptions.Condition condition, byte[]... fields) { return hashCommands().hpExpire(key, millis, condition, fields); } @@ -1511,13 +1538,13 @@ default List hpExpire(byte[] key, long millis, FieldExpirationOptions.Cond @Override @Deprecated default List 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 hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, + default List hExpireAt(byte[] key, long unixTime, ExpirationOptions.Condition condition, byte[]... fields) { return hashCommands().hExpireAt(key, unixTime, condition, fields); } @@ -1526,13 +1553,13 @@ default List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.C @Override @Deprecated default List 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 hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + default List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition, byte[]... fields) { return hashCommands().hpExpireAt(key, unixTimeInMillis, condition, fields); } @@ -1568,10 +1595,10 @@ default List hpTtl(byte[] key, byte[]... fields) { /** @deprecated in favor of {@link RedisConnection#hashCommands()}}. */ @Override @Deprecated - default @Nullable List applyExpiration(byte[] key, - org.springframework.data.redis.core.types.Expiration expiration, FieldExpirationOptions options, + default @Nullable List 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 diff --git a/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java b/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java new file mode 100644 index 0000000000..900f27bd9d --- /dev/null +++ b/src/main/java/org/springframework/data/redis/connection/ExpirationOptions.java @@ -0,0 +1,157 @@ +/* + * 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 ExpirationOptionsBuilder builder() { + return new ExpirationOptionsBuilder(); + } + + 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 ExpirationOptionsBuilder { + + private Condition condition = Condition.ALWAYS; + + private ExpirationOptionsBuilder() {} + + /** + * Apply to fields that have no expiration. + */ + @Contract("-> this") + public ExpirationOptionsBuilder nx() { + this.condition = Condition.NX; + return this; + } + + /** + * Apply to fields that have an existing expiration. + */ + @Contract("-> this") + public ExpirationOptionsBuilder xx() { + this.condition = Condition.XX; + return this; + } + + /** + * Apply to fields when the new expiration is greater than the current one. + */ + @Contract("-> this") + public ExpirationOptionsBuilder gt() { + this.condition = Condition.GT; + return this; + } + + /** + * Apply to fields when the new expiration is lower than the current one. + */ + @Contract("-> this") + public ExpirationOptionsBuilder 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 + + } + +} diff --git a/src/main/java/org/springframework/data/redis/connection/Hash.java b/src/main/java/org/springframework/data/redis/connection/Hash.java deleted file mode 100644 index 51e326dd2b..0000000000 --- a/src/main/java/org/springframework/data/redis/connection/Hash.java +++ /dev/null @@ -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 - - } - - } - -} diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java index c463737747..385b652f2b 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveHashCommands.java @@ -31,7 +31,6 @@ 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 @@ default Mono hStrLen(ByteBuffer key, ByteBuffer field) { /** * @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 fields, Expiration expiration, - FieldExpirationOptions options) { + private HashExpireCommand(@Nullable ByteBuffer key, List fields, Expiration expiration, + ExpirationOptions options) { super(key, fields); @@ -866,52 +865,52 @@ private ExpireCommand(@Nullable ByteBuffer key, List fields, Expirat } /** - * 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 fields, long timeout, TimeUnit unit) { + public static HashExpireCommand expire(List 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 fields, Duration ttl) { + public static HashExpireCommand expire(List 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 fields, Expiration expiration) { - return new ExpireCommand(null, fields, expiration, FieldExpirationOptions.none()); + public static HashExpireCommand expire(List 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 fields, Instant ttl, TimeUnit precision) { + public static HashExpireCommand expireAt(List 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 static ExpireCommand expireAt(List fields, Instant ttl, TimeU /** * @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 @@ default Flux hExpire(ByteBuffer key, Duration duration, List f 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 @@ default Flux hExpire(ByteBuffer key, Duration duration, List f * @since 3.5 * @see Redis Documentation: HEXPIRE */ - Flux> applyExpiration(Publisher commands); + Flux> applyHashFieldExpiration(Publisher commands); /** * Expire a given {@literal field} after a given {@link Duration} of time, measured in milliseconds, has passed. @@ -1039,8 +1038,8 @@ default Flux hpExpire(ByteBuffer key, Duration duration, List 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 @@ default Flux hExpireAt(ByteBuffer key, Instant expireAt, List 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 @@ default Flux hpExpireAt(ByteBuffer key, Instant expireAt, List 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); } diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java index 91b81640c1..62be2393ef 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveKeyCommands.java @@ -25,6 +25,7 @@ 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.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 @@ default Mono mUnlink(List keys) { */ class ExpireCommand extends KeyCommand { - private @Nullable Duration timeout; + private final Expiration expiration; + private final ExpirationOptions options; + + private ExpireCommand(ByteBuffer key, Duration timeout) { + this(key, Expiration.from(timeout), ExpirationOptions.none()); + } - private ExpireCommand(ByteBuffer key, @Nullable Duration timeout) { + 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 static ExpireCommand key(ByteBuffer key) { 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 ExpireCommand timeout(Duration timeout) { 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 ExpireCommand timeout(Duration timeout) { */ @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 Redis Documentation: EXPIRE + * @see Redis Documentation: PEXPIRE + * @see Redis Documentation: EXPIREAT + * @see Redis Documentation: PEXPIREAT + * @see Redis Documentation: PERSIST + */ + Flux> applyExpiration(Publisher commands); + /** * Set time to live for given {@code key} in seconds. * @@ -581,7 +659,9 @@ default Mono expire(ByteBuffer key, Duration timeout) { * result. * @see Redis Documentation: EXPIRE */ - Flux> expire(Publisher commands); + default Flux> expire(Publisher commands) { + return applyExpiration(commands); + } /** * Set time to live for given {@code key} in milliseconds. @@ -607,7 +687,9 @@ default Mono pExpire(ByteBuffer key, Duration timeout) { * result. * @see Redis Documentation: PEXPIRE */ - Flux> pExpire(Publisher commands); + default Flux> pExpire(Publisher commands) { + return applyExpiration(commands); + } /** * {@code EXPIREAT}/{@code PEXPIREAT} command parameters. @@ -619,12 +701,18 @@ default Mono pExpire(ByteBuffer key, Duration timeout) { 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 ExpireAtCommand timeout(Instant expireAt) { 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 ExpireAtCommand timeout(Instant expireAt) { public Instant getExpireAt() { return expireAt; } + + public ExpirationOptions getOptions() { + return options; + } + } /** diff --git a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java index d038708526..cd99e4a516 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisHashCommands.java @@ -21,7 +21,6 @@ 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 applyExpiration(byte[] key, + default @Nullable List 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 applyExpiration(byte[] key, org.springframework.data.redis.core.types.Expiration expiration, - FieldExpirationOptions options, byte[]... fields) { + default List 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 @@ default List applyExpiration(byte[] key, org.springframework.data.redis.co */ @Nullable default List 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 @@ default List hExpire(byte[] key, Duration ttl, byte[]... fields) { * @since 3.5 */ @Nullable - List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields); + List hExpire(byte[] key, long seconds, ExpirationOptions.Condition condition, byte[]... fields); /** * Set time to live for given {@code fields} in milliseconds. @@ -390,7 +389,7 @@ default List hExpire(byte[] key, Duration ttl, byte[]... fields) { */ @Nullable default List 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 @@ default List hpExpire(byte[] key, Duration ttl, byte[]... fields) { * @since 3.5 */ @Nullable - List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields); + List 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 @@ default List hpExpire(byte[] key, Duration ttl, byte[]... fields) { */ @Nullable default List 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 @@ default List hExpireAt(byte[] key, long unixTime, byte[]... fields) { * @since 3.5 */ @Nullable - List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields); + List 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 @@ default List hExpireAt(byte[] key, long unixTime, byte[]... fields) { */ @Nullable default List 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 @@ default List hpExpireAt(byte[] key, long unixTimeInMillis, byte[]... field * @since 3.5 */ @Nullable - List hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition, byte[]... fields); /** diff --git a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java index 49326637d3..4319dd8705 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisKeyCommands.java @@ -26,6 +26,7 @@ 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 @@ default Cursor scan(KeyScanOptions options) { @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 Redis Documentation: EXPIRE + * @see Redis Documentation: PEXPIRE + * @see Redis Documentation: EXPIREAT + * @see Redis Documentation: PEXPIREAT + * @see Redis Documentation: PERSIST + */ + @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 Redis Documentation: EXPIRE + */ + @Nullable + 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 Redis Documentation: EXPIRE + * @since 3.5 */ @Nullable - Boolean expire(byte[] key, long seconds); + 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 Redis Documentation: EXPIRE * @since 3.5 */ @@ -211,18 +282,38 @@ default Boolean expire(byte[] key, Duration duration) { * * @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 Redis Documentation: PEXPIRE + */ + @Nullable + 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 Redis Documentation: PEXPIRE + * @since 3.5 */ @Nullable - Boolean pExpire(byte[] key, long millis); + 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 Redis Documentation: PEXPIRE * @since 3.5 */ @@ -236,11 +327,29 @@ default Boolean pExpire(byte[] key, Duration duration) { * * @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 Redis Documentation: EXPIREAT + */ + @Nullable + 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 Redis Documentation: EXPIREAT + * @since 3.5 */ @Nullable - Boolean expireAt(byte[] key, long unixTime); + 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 @@ default Boolean pExpire(byte[] key, Duration duration) { * * @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 Redis Documentation: EXPIREAT * @since 3.5 */ @@ -262,11 +373,29 @@ default Boolean expireAt(byte[] key, Instant unixTime) { * * @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 Redis Documentation: PEXPIREAT + */ + @Nullable + 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 Redis Documentation: PEXPIREAT + * @since 3.5 */ @Nullable - Boolean pExpireAt(byte[] key, long unixTimeInMillis); + 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 @@ default Boolean expireAt(byte[] key, Instant unixTime) { * * @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 Redis Documentation: PEXPIREAT * @since 3.5 */ diff --git a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java index 1069e430c8..dfcc585130 100644 --- a/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/StringRedisConnection.java @@ -224,7 +224,22 @@ interface StringTuple extends Tuple { * @see Redis Documentation: EXPIRE * @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 Redis Documentation: EXPIRE + * @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 @@ interface StringTuple extends Tuple { * @see Redis Documentation: PEXPIRE * @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 Redis Documentation: PEXPIRE + * @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 @@ interface StringTuple extends Tuple { * @see Redis Documentation: EXPIREAT * @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 Redis Documentation: EXPIREAT + * @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 @@ interface StringTuple extends Tuple { * @see Redis Documentation: PEXPIREAT * @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 Redis Documentation: PEXPIREAT + * @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 @@ Long zRangeStoreRevByScore(String dstKey, String srcKey, */ @Nullable default List 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 @@ default List hExpire(String key, long seconds, String... fields) { * @since 3.5 */ @Nullable - List hExpire(String key, long seconds, Hash.FieldExpirationOptions.Condition condition, String... fields); + List hExpire(String key, long seconds, ExpirationOptions.Condition condition, String... fields); /** * Set time to live for given {@code field} in milliseconds. @@ -2383,7 +2443,7 @@ default List hExpire(String key, long seconds, String... fields) { */ @Nullable default List 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 @@ default List hpExpire(String key, long millis, String... fields) { * @since 3.5 */ @Nullable - List hpExpire(String key, long millis, Hash.FieldExpirationOptions.Condition condition, String... fields); + List 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 @@ default List hpExpire(String key, long millis, String... fields) { */ @Nullable default List 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 @@ default List hExpireAt(String key, long unixTime, String... fields) { * @since 3.5 */ @Nullable - List hExpireAt(String key, long unixTime, Hash.FieldExpirationOptions.Condition condition, String... fields); + List 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 @@ default List hExpireAt(String key, long unixTime, String... fields) { */ @Nullable default List 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 @@ default List hpExpireAt(String key, long unixTimeInMillis, String... field * @since 3.5 */ @Nullable - List hpExpireAt(String key, long unixTimeInMillis, Hash.FieldExpirationOptions.Condition condition, + List hpExpireAt(String key, long unixTimeInMillis, ExpirationOptions.Condition condition, String... fields); /** diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterHashCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterHashCommands.java index 1223ab4c06..551a17d153 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterHashCommands.java @@ -27,7 +27,7 @@ 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 @@ protected ScanIteration> doScan(CursorId cursorId, ScanOpt } @Override - public List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condi } @Override - public List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condi } @Override - public List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Co } @Override - public List hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + public List hpExpireAt(byte[] key, long unixTimeInMillis, ExpirationOptions.Condition condition, byte[]... fields) { Assert.notNull(key, "Key must not be null"); @@ -350,7 +350,7 @@ public List hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationO try { - if (condition == FieldExpirationOptions.Condition.ALWAYS) { + if (condition == ExpirationOptions.Condition.ALWAYS) { return connection.getCluster().hpexpireAt(key, unixTimeInMillis, fields); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java index cb1b07e9c3..559c15bf86 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterKeyCommands.java @@ -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.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 @@ public Boolean renameNX(byte[] sourceKey, byte[] targetKey) { } @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); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisHashCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisHashCommands.java index 2e83d8aba0..33189c27b4 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisHashCommands.java @@ -29,7 +29,7 @@ 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 @@ protected void doClose() { } @Override - public List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condi } @Override - public List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hpExpire(byte[] key, long millis, FieldExpirationOptions.Condi } @Override - public List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 @@ public List hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Co } @Override - public List hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + public List 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); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java index 93f0ddfff6..9d4465b410 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisKeyCommands.java @@ -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 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 @@ public Boolean renameNX(byte[] sourceKey, byte[] targetKey) { } @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) + 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::expireAt, PipelineBinaryCommands::expireAt, key, unixTime, option) .get(JedisConverters.longToBoolean()); } @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"); + 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) + .from(JedisBinaryCommands::pexpireAt, PipelineBinaryCommands::pexpireAt, key, unixTimeInMillis, option) .get(JedisConverters.longToBoolean()); } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceHashCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceHashCommands.java index 032d6230d6..278671704b 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceHashCommands.java @@ -29,7 +29,7 @@ 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 @@ public Cursor> hScan(byte[] key, ScanOptions options) { } @Override - public List hExpire(byte[] key, long seconds, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 hpExpire(byte[] key, long millis, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 hExpireAt(byte[] key, long unixTime, FieldExpirationOptions.Condition condition, byte[]... fields) { + public List 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 hpExpireAt(byte[] key, long unixTimeInMillis, FieldExpirationOptions.Condition condition, + public List 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 @@ private static Entry toEntry(KeyValue value) { 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 void build(CommandArgs args) { - if (ObjectUtils.nullSafeEquals(condition, FieldExpirationOptions.Condition.ALWAYS)) { + if (ObjectUtils.nullSafeEquals(condition, ExpirationOptions.Condition.ALWAYS)) { return; } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java index a9514cd793..78d4e7006e 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceKeyCommands.java @@ -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 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.ScanOptions; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl @@ -192,35 +196,35 @@ public Boolean renameNX(byte[] sourceKey, byte[] targetKey) { } @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 @@ public Long refcount(byte[] key) { return connection.invoke().just(RedisKeyAsyncCommands::objectRefcount, key); } + + private static ExpireArgs getExpireArgs(ExpirationOptions.Condition condition) { + + return new ExpireArgs() { + @Override + public void build(CommandArgs args) { + + if (ObjectUtils.nullSafeEquals(condition, ExpirationOptions.Condition.ALWAYS)) { + return; + } + + args.add(condition.name()); + } + }; + } } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveHashCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveHashCommands.java index 84dd2ca906..e637296219 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveHashCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveHashCommands.java @@ -31,7 +31,7 @@ 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 @@ public Flux> hStrLen(Publisher> applyExpiration(Publisher commands) { + public Flux> applyHashFieldExpiration( + Publisher commands) { return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { @@ -287,7 +288,7 @@ public Flux> applyExpiration(Publisher void build(CommandArgs args) { super.build(args); - if (ObjectUtils.nullSafeEquals(command.getOptions(), FieldExpirationOptions.none())) { + if (ObjectUtils.nullSafeEquals(command.getOptions(), ExpirationOptions.none())) { return; } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java index a1371b7856..3dbb44c69d 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommands.java @@ -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.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.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 @@ public Flux, Long>> mUnlink(Publisher> expire(Publisher commands) { + public Flux> applyExpiration(Publisher 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> pExpire(Publisher commands) { + ExpireArgs args = new ExpireArgs() { - return connection.execute(cmd -> Flux.from(commands).concatMap(command -> { + @Override + public void build(CommandArgs 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()); + } + }; + + 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.pexpire(command.getKey(), command.getTimeout().toMillis()) + return cmd.expire(command.getKey(), command.getExpiration().getExpirationTimeInSeconds(), args) .map(value -> new BooleanResponse<>(command, value)); })); } @@ -239,7 +263,7 @@ public Flux> expireAt(Publisher new BooleanResponse<>(command, value)); })); } @@ -252,7 +276,7 @@ public Flux> pExpireAt(Publisher new BooleanResponse<>(command, value)); })); } @@ -319,4 +343,21 @@ public Mono idletime(ByteBuffer key) { public Mono refcount(ByteBuffer key) { return connection.execute(cmd -> cmd.objectRefcount(key)).next(); } + + private static ExpireArgs getExpireArgs(ExpirationOptions options) { + + return new ExpireArgs() { + + @Override + public void build(CommandArgs args) { + super.build(args); + if (ObjectUtils.nullSafeEquals(options.getCondition(), ExpirationOptions.Condition.ALWAYS)) { + return; + } + + args.add(options.getCondition().name()); + } + }; + } + } diff --git a/src/main/java/org/springframework/data/redis/core/BoundHashFieldExpirationOperations.java b/src/main/java/org/springframework/data/redis/core/BoundHashFieldExpirationOperations.java index d49041a66d..8546445af4 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundHashFieldExpirationOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundHashFieldExpirationOperations.java @@ -19,7 +19,7 @@ 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 { * @return changes to the hash fields. {@literal null} when used in pipeline / transaction. */ default ExpireChanges 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 expire(Expiration expiration, Hash.FieldExpirationOptions options); + ExpireChanges expire(Expiration expiration, ExpirationOptions options); /** * Set time to live for the bound hash key/hash fields. diff --git a/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java b/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java index 0d287e929f..0355588ea4 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundHashOperations.java @@ -223,7 +223,7 @@ public interface BoundHashOperations extends BoundKeyOperations { * @return the bound operations object to perform operations on the hash field expiration. * @since 3.5 */ - default BoundHashFieldExpirationOperations expiration() { + default BoundHashFieldExpirationOperations hashExpiration() { return new DefaultBoundHashFieldExpirationOperations<>(getOperations().opsForHash(), getKey(), this::keys); } @@ -235,8 +235,8 @@ default BoundHashFieldExpirationOperations expiration() { * @return the bound operations object to perform operations on the hash field expiration. * @since 3.5 */ - default BoundHashFieldExpirationOperations expiration(HK... hashFields) { - return expiration(Arrays.asList(hashFields)); + default BoundHashFieldExpirationOperations hashExpiration(HK... hashFields) { + return hashExpiration(Arrays.asList(hashFields)); } /** @@ -247,7 +247,7 @@ default BoundHashFieldExpirationOperations expiration(HK... hashFields) { * @return the bound operations object to perform operations on the hash field expiration. * @since 3.5 */ - default BoundHashFieldExpirationOperations expiration(Collection hashFields) { + default BoundHashFieldExpirationOperations hashExpiration(Collection hashFields) { return new DefaultBoundHashFieldExpirationOperations<>(getOperations().opsForHash(), getKey(), () -> hashFields); } diff --git a/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java b/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java new file mode 100644 index 0000000000..b50431b209 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/core/BoundKeyExpirationOperations.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +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; + +/** + * Key Expiration operations bound to a key. + * + * @author Mark Paluch + * @since 3.5 + */ +public interface BoundKeyExpirationOperations { + + /** + * Apply {@link Expiration} to the bound key without any additional constraints. + * + * @param expiration the expiration definition. + * @return changes to the key. {@literal null} when used in pipeline / transaction. + */ + default ExpireChanges.ExpiryChangeState expire(Expiration expiration) { + return expire(expiration, ExpirationOptions.none()); + } + + /** + * Apply {@link Expiration} to the bound key given {@link ExpirationOptions expiration options}. + * + * @param expiration the expiration definition. + * @param options expiration options. + * @return changes to the key. {@literal null} when used in pipeline / transaction. + */ + @Nullable + ExpireChanges.ExpiryChangeState expire(Expiration expiration, ExpirationOptions options); + + /** + * Set time to live for the bound key. + * + * @param timeout the amount of time after which the key will be expired, must not be {@literal null}. + * @return changes to the key. {@literal null} when used in pipeline / transaction. + * @throws IllegalArgumentException if the timeout is {@literal null}. + * @see Redis Documentation: EXPIRE + * @since 3.5 + */ + @Nullable + ExpireChanges.ExpiryChangeState expire(Duration timeout); + + /** + * Set the expiration for the bound key as a {@literal date} timestamp. + * + * @param expireAt must not be {@literal null}. + * @return changes to the key. {@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 Redis Documentation: EXPIRE + * @since 3.5 + */ + @Nullable + ExpireChanges.ExpiryChangeState expireAt(Instant expireAt); + + /** + * Remove the expiration from the bound key. + * + * @return changes to the key. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: PERSIST + * @since 3.5 + */ + @Nullable + ExpireChanges.ExpiryChangeState persist(); + + /** + * Get the time to live for the bound key in seconds. + * + * @return the actual expirations in seconds for the key. {@literal null} when used in pipeline / transaction. + * @see Redis Documentation: TTL + * @since 3.5 + */ + @Nullable + Expirations.TimeToLive getTimeToLive(); + + /** + * Get the time to live for the bound key and convert it to the given {@link TimeUnit}. + * + * @param timeUnit must not be {@literal null}. + * @return the actual expirations for the key in the given time unit. {@literal null} when used in pipeline / + * transaction. + * @see Redis Documentation: TTL + * @since 3.5 + */ + @Nullable + Expirations.TimeToLive getTimeToLive(TimeUnit timeUnit); + +} diff --git a/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java b/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java index e9d1f5e57c..bb8b438a75 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java +++ b/src/main/java/org/springframework/data/redis/core/BoundKeyOperations.java @@ -51,6 +51,16 @@ public interface BoundKeyOperations { @Nullable DataType getType(); + /** + * Returns a bound operations object to perform expiration operations on the bound key. + * + * @return the bound operations object to perform operations on the hash field expiration. + * @since 3.5 + */ + default BoundKeyExpirationOperations expiration() { + return new DefaultBoundKeyExpirationOperations<>(getOperations(), getKey()); + } + /** * Returns the expiration of this key. * @@ -127,4 +137,10 @@ default Boolean expireAt(Instant expireAt) { * @param newKey new key. Must not be {@literal null}. */ void rename(K newKey); + + /** + * @return never {@literal null}. + */ + RedisOperations getOperations(); + } diff --git a/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java index 3492553b28..2c4bfc5f49 100644 --- a/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java +++ b/src/main/java/org/springframework/data/redis/core/BoundOperationsProxyFactory.java @@ -143,7 +143,7 @@ public Object invoke(MethodInvocation invocation) throws Throwable { delegate.rename(invocation.getArguments()[0]); yield null; } - case "getOperations" -> delegate.getOps(); + case "getOperations" -> delegate.getOperations(); default -> method.getDeclaringClass() == boundOperationsInterface ? doInvoke(invocation, method, operationsTarget, true) : doInvoke(invocation, method, delegate, false); @@ -234,12 +234,15 @@ public void rename(Object newKey) { key = newKey; } + @Override public DataType getType() { return type; } - public RedisOperations getOps() { + @Override + public RedisOperations getOperations() { return ops; } + } } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashFieldExpirationOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundHashFieldExpirationOperations.java index 8dbabe6dd5..fdb45c57bc 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultBoundHashFieldExpirationOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundHashFieldExpirationOperations.java @@ -21,7 +21,7 @@ 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 @@ public DefaultBoundHashFieldExpirationOperations(HashOperations operat } @Override - public ExpireChanges expire(Expiration expiration, Hash.FieldExpirationOptions options) { + public ExpireChanges expire(Expiration expiration, ExpirationOptions options) { return operations.expire(key, expiration, options, getHashKeys()); } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyExpirationOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyExpirationOperations.java new file mode 100644 index 0000000000..9ff186cd3e --- /dev/null +++ b/src/main/java/org/springframework/data/redis/core/DefaultBoundKeyExpirationOperations.java @@ -0,0 +1,99 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.redis.core; + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +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; + +/** + * Default {@link BoundKeyExpirationOperations} implementation. + * + * @author Mark Paluch + * @since 3.5 + */ +class DefaultBoundKeyExpirationOperations implements BoundKeyExpirationOperations { + + private final RedisOperations operations; + private final K key; + + public DefaultBoundKeyExpirationOperations(RedisOperations operations, K key) { + this.operations = operations; + this.key = key; + } + + @Nullable + @Override + public ExpireChanges.ExpiryChangeState expire(Expiration expiration, ExpirationOptions options) { + return operations.expire(key, expiration, options); + } + + @Nullable + @Override + public ExpireChanges.ExpiryChangeState expire(Duration timeout) { + + Boolean expire = operations.expire(key, timeout); + + return toExpiryChangeState(expire); + } + + @Nullable + @Override + public ExpireChanges.ExpiryChangeState expireAt(Instant expireAt) { + return toExpiryChangeState(operations.expireAt(key, expireAt)); + } + + @Nullable + @Override + public ExpireChanges.ExpiryChangeState persist() { + return toExpiryChangeState(operations.persist(key)); + } + + @Nullable + @Override + public Expirations.TimeToLive getTimeToLive() { + + Long expire = operations.getExpire(key); + + return expire == null ? null : Expirations.TimeToLive.of(expire, TimeUnit.SECONDS); + } + + @Nullable + @Override + public Expirations.TimeToLive getTimeToLive(TimeUnit timeUnit) { + + Long expire = operations.getExpire(key, timeUnit); + + return expire == null ? null : Expirations.TimeToLive.of(expire, timeUnit); + + } + + @Nullable + private static ExpireChanges.ExpiryChangeState toExpiryChangeState(@Nullable Boolean result) { + + if (result == null) { + return null; + } + + return ExpireChanges.ExpiryChangeState.of(result); + } + +} diff --git a/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java index 804617616f..ccdfbee704 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultHashOperations.java @@ -27,7 +27,7 @@ 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 @@ public ExpireChanges expireAt(K key, Instant instant, Collection hashKey } @Override - public ExpireChanges expire(K key, Expiration expiration, FieldExpirationOptions options, Collection hashKeys) { + public ExpireChanges expire(K key, Expiration expiration, ExpirationOptions options, Collection hashKeys) { List orderedKeys = List.copyOf(hashKeys); byte[] rawKey = rawKey(key); byte[][] rawHashKeys = rawHashKeys(orderedKeys.toArray()); List 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; } diff --git a/src/main/java/org/springframework/data/redis/core/DefaultReactiveHashOperations.java b/src/main/java/org/springframework/data/redis/core/DefaultReactiveHashOperations.java index 540b351778..282f229913 100644 --- a/src/main/java/org/springframework/data/redis/core/DefaultReactiveHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/DefaultReactiveHashOperations.java @@ -30,9 +30,9 @@ 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 @@ public Flux> scan(H key, ScanOptions options) { @Override public Mono> expire(H key, Duration timeout, Collection hashKeys) { - return expire(key, Expiration.from(timeout), FieldExpirationOptions.none(), hashKeys); + return expire(key, Expiration.from(timeout), ExpirationOptions.none(), hashKeys); } @Override - public Mono> expire(H key, Expiration expiration, FieldExpirationOptions options, + public Mono> expire(H key, Expiration expiration, ExpirationOptions options, Collection hashKeys) { List orderedKeys = List.copyOf(hashKeys); @@ -257,7 +257,8 @@ public Mono> expire(H key, Expiration expiration, FieldExpirat Mono> 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(); diff --git a/src/main/java/org/springframework/data/redis/core/ExpireChanges.java b/src/main/java/org/springframework/data/redis/core/ExpireChanges.java index 029922d96e..10bef1e2d2 100644 --- a/src/main/java/org/springframework/data/redis/core/ExpireChanges.java +++ b/src/main/java/org/springframework/data/redis/core/ExpireChanges.java @@ -151,6 +151,10 @@ public record ExpiryChangeState(long value) { 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; diff --git a/src/main/java/org/springframework/data/redis/core/HashOperations.java b/src/main/java/org/springframework/data/redis/core/HashOperations.java index f57143c737..1dc34db4c3 100644 --- a/src/main/java/org/springframework/data/redis/core/HashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/HashOperations.java @@ -24,7 +24,7 @@ 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 { ExpireChanges expireAt(H key, Instant expireAt, Collection 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 { * @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 Redis Documentation: HEXPIRE + * @see Redis Documentation: HEXPIRE + * @see Redis Documentation: HPEXPIRE + * @see Redis Documentation: HEXPIREAT + * @see Redis Documentation: HPEXPIREAT + * @see Redis Documentation: HPERSIST * @since 3.5 */ @Nullable - ExpireChanges expire(H key, Expiration expiration, FieldExpirationOptions options, Collection hashKeys); + ExpireChanges expire(H key, Expiration expiration, ExpirationOptions options, Collection hashKeys); /** * Remove the expiration from given {@code hashKeys} . diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveHashOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveHashOperations.java index 2fe3b0c65b..4c8c0986d8 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveHashOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveHashOperations.java @@ -26,7 +26,7 @@ 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 @@ default Flux> scan(H key) { * @see Redis Documentation: HEXPIRE * @since 3.5 */ - Mono> expire(H key, Expiration expiration, FieldExpirationOptions options, Collection hashKeys); + Mono> expire(H key, Expiration expiration, ExpirationOptions options, Collection hashKeys); /** * Set the expiration for given {@code hashKey} as a {@literal date} timestamp. diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java index f027c46366..996ddbc584 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisOperations.java @@ -26,9 +26,12 @@ 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 @@ default Flux scan() { */ Mono 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 any of required arguments is {@literal null}. + * @see Redis Documentation: EXPIRE + * @see Redis Documentation: PEXPIRE + * @see Redis Documentation: EXPIREAT + * @see Redis Documentation: PEXPIREAT + * @see Redis Documentation: PERSIST + * @since 3.5 + */ + Mono expire(K key, Expiration expiration, ExpirationOptions options); + /** * Remove the expiration from given {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java index 92792ed81b..0649072b4b 100644 --- a/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/ReactiveRedisTemplate.java @@ -29,6 +29,8 @@ 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.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 Mono expireAt(K key, Instant expireAt) { return doCreateMono(connection -> connection.keyCommands().pExpireAt(rawKey(key), expireAt)); } + @Override + public Mono 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 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 persist(K key) { diff --git a/src/main/java/org/springframework/data/redis/core/RedisCommand.java b/src/main/java/org/springframework/data/redis/core/RedisCommand.java index 53dc940a97..1454fd33c2 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisCommand.java +++ b/src/main/java/org/springframework/data/redis/core/RedisCommand.java @@ -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), // diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java index 8c1ad67ad6..4ea682d900 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -25,10 +25,12 @@ 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,34 @@ default Boolean expireAt(K key, Instant expireAt) { 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 any of the required arguments is {@literal null}. + * @see Redis Documentation: EXPIRE + * @see Redis Documentation: PEXPIRE + * @see Redis Documentation: EXPIREAT + * @see Redis Documentation: PEXPIREAT + * @see Redis Documentation: PERSIST + * @since 3.5 + */ + @Nullable + ExpireChanges.ExpiryChangeState expire(K key, Expiration expiration, ExpirationOptions options); + + /** + * Returns a bound operations object to perform expiration operations on the bound key. + * + * @return the bound operations object to perform operations on the hash field expiration. + * @since 3.5 + */ + default BoundKeyExpirationOperations expiration(K key) { + return new DefaultBoundKeyExpirationOperations<>(this, key); + } + /** * Remove the expiration from given {@code key}. * diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index 2879f161a4..c80f555ee0 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -33,6 +33,7 @@ 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.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 Boolean expireAt(K key, final Date date) { }); } + @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) { diff --git a/src/main/java/org/springframework/data/redis/core/types/Expiration.java b/src/main/java/org/springframework/data/redis/core/types/Expiration.java index a68dd516b8..2e4627dbc5 100644 --- a/src/main/java/org/springframework/data/redis/core/types/Expiration.java +++ b/src/main/java/org/springframework/data/redis/core/types/Expiration.java @@ -16,7 +16,6 @@ package org.springframework.data.redis.core.types; import java.time.Duration; -import java.util.Objects; import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.TimeoutUtils; diff --git a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicDouble.java b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicDouble.java index a60d55ad3c..a95f78b3e5 100644 --- a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicDouble.java +++ b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicDouble.java @@ -399,6 +399,11 @@ public void rename(String newKey) { key = newKey; } + @Override + public RedisOperations getOperations() { + return generalOps; + } + @Override public int intValue() { return (int) get(); @@ -418,4 +423,5 @@ public float floatValue() { public double doubleValue() { return get(); } + } diff --git a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicInteger.java b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicInteger.java index ffdb6ff50d..a4f3e65dba 100644 --- a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicInteger.java +++ b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicInteger.java @@ -399,6 +399,11 @@ public void rename(String newKey) { key = newKey; } + @Override + public RedisOperations getOperations() { + return generalOps; + } + @Override public int intValue() { return get(); diff --git a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicLong.java b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicLong.java index df7f96035d..2e488697ae 100644 --- a/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicLong.java +++ b/src/main/java/org/springframework/data/redis/support/atomic/RedisAtomicLong.java @@ -396,6 +396,11 @@ public void rename(String newKey) { key = newKey; } + @Override + public RedisOperations getOperations() { + return generalOps; + } + @Override public int intValue() { return (int) get(); @@ -415,4 +420,5 @@ public float floatValue() { public double doubleValue() { return get(); } + } diff --git a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java index 18c3c24e20..3233ec59ac 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java +++ b/src/main/java/org/springframework/data/redis/support/collections/DefaultRedisMap.java @@ -324,13 +324,13 @@ public Cursor> scan() { } @Override - public BoundHashFieldExpirationOperations expiration() { - return hashOps.expiration(); + public BoundHashFieldExpirationOperations hashFieldExpiration() { + return hashOps.hashExpiration(); } @Override - public BoundHashFieldExpirationOperations expiration(Collection hashFields) { - return hashOps.expiration(hashFields); + public BoundHashFieldExpirationOperations hashFieldExpiration(Collection hashFields) { + return hashOps.hashExpiration(hashFields); } private void checkResult(@Nullable Object obj) { diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java b/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java index 955942eb25..d9c8c0a35e 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisMap.java @@ -78,34 +78,35 @@ public interface RedisMap extends RedisStore, ConcurrentMap { Iterator> 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 expiration(); + BoundHashFieldExpirationOperations 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 expiration(K... hashFields) { - return expiration(Arrays.asList(hashFields)); + default BoundHashFieldExpirationOperations 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 expiration(Collection hashFields); + BoundHashFieldExpirationOperations hashFieldExpiration(Collection hashFields); } diff --git a/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java b/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java index c6a5f0a45d..5d3534fb6e 100644 --- a/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java +++ b/src/main/java/org/springframework/data/redis/support/collections/RedisProperties.java @@ -307,13 +307,13 @@ public Iterator> scan() { } @Override - public BoundHashFieldExpirationOperations expiration() { - return (BoundHashFieldExpirationOperations) delegate.expiration(); + public BoundHashFieldExpirationOperations hashFieldExpiration() { + return (BoundHashFieldExpirationOperations) delegate.hashFieldExpiration(); } @Override - public BoundHashFieldExpirationOperations expiration(Collection hashFields) { - return (BoundHashFieldExpirationOperations) delegate.expiration((Collection) hashFields); + public BoundHashFieldExpirationOperations hashFieldExpiration(Collection hashFields) { + return (BoundHashFieldExpirationOperations) delegate.hashFieldExpiration((Collection) hashFields); } } diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 573f105247..288aa202b0 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -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.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 @@ void testExpire() { 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 @@ void testExecute() { 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 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() { diff --git a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java index ad64444cc1..193ac17190 100644 --- a/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/ClusterConnectionTests.java @@ -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(); diff --git a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java index 4efcf03168..5e9c1b38e6 100644 --- a/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/DefaultStringRedisConnectionTests.java @@ -273,28 +273,28 @@ public void testExists() { @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 void testZUnionStore() { @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)); } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java index 4bed88c0a5..b4229d9522 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionTests.java @@ -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.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.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 void expireAtShouldBeSetCorrectly() { 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 void expireShouldBeSetCorreclty() { 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 void pExpireAtShouldBeSetCorrectly() { 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 void pExpireShouldBeSetCorreclty() { 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() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java index 1d45dc739e..5590b18275 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionTests.java @@ -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 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.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.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 void expireAtShouldBeSetCorrectly() { 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 void expireShouldBeSetCorreclty() { 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 void pExpireAtShouldBeSetCorrectly() { 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 void pExpireShouldBeSetCorreclty() { 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() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsIntegrationTests.java index 3376c2727f..bc16ae8a46 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveKeyCommandsIntegrationTests.java @@ -32,11 +32,15 @@ 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 @@ void shouldExpireKeysCorrectly() { 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() { diff --git a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsIntegrationTests.java index 6499ae325a..4982e72973 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultHashOperationsIntegrationTests.java @@ -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 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 @@ void testBoundExpireAndGetExpireSeconds() { hashOps.put(key, key2, val2); BoundHashOperations hashOps = redisTemplate.boundHashOps(key); - BoundHashFieldExpirationOperations exp = hashOps.expiration(key1, key2); + BoundHashFieldExpirationOperations exp = hashOps.hashExpiration(key1, key2); assertThat(exp.expire(Duration.ofSeconds(5))).satisfies(changes -> { assertThat(changes.allOk()).isTrue(); @@ -350,7 +350,7 @@ void testExpireWithOptionsNone() { hashOps.put(key, key2, val2); ExpireChanges 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 @@ void testExpireWithOptions() { 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 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); diff --git a/src/test/java/org/springframework/data/redis/core/DefaultReactiveHashOperationsIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/DefaultReactiveHashOperationsIntegrationTests.java index 48532b2feb..a387f2ae3c 100644 --- a/src/test/java/org/springframework/data/redis/core/DefaultReactiveHashOperationsIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/DefaultReactiveHashOperationsIntegrationTests.java @@ -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 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 @@ void testExpireWithOptions() { 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 @@ void testExpireWithOptions() { 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); diff --git a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java index bd8eb0b141..f3c55deb55 100644 --- a/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/ReactiveRedisTemplateIntegrationTests.java @@ -37,6 +37,7 @@ 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.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 @@ void expire() { .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 @@ void preciseExpire() { .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() { diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java index 2bb8046f87..56f99db3ba 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateIntegrationTests.java @@ -34,6 +34,7 @@ import org.springframework.data.redis.Person; import org.springframework.data.redis.SettingsUtils; import org.springframework.data.redis.connection.DataType; +import org.springframework.data.redis.connection.ExpirationOptions; import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.StringRedisConnection; @@ -42,11 +43,13 @@ import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.data.redis.core.query.SortQueryBuilder; import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.GenericToStringSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.test.condition.EnabledIfLongRunningTest; +import org.springframework.data.redis.test.condition.EnabledOnCommand; import org.springframework.data.redis.test.extension.LettuceTestClientResources; import org.springframework.data.redis.test.extension.parametrized.MethodSource; import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest; @@ -503,6 +506,46 @@ void testExpireAndGetExpireMillis() { assertThat(redisTemplate.getExpire(key1, TimeUnit.MILLISECONDS)).isGreaterThan(0L); } + @ParameterizedRedisTest // GH-3114 + @EnabledOnCommand("SPUBLISH") // Redis 7.0 + void testBoundExpireAndGetExpireSeconds() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + redisTemplate.boundValueOps(key).set(value1); + + BoundKeyExpirationOperations exp = redisTemplate.expiration(key); + + assertThat(exp.expire(Duration.ofSeconds(5))).isEqualTo(ExpireChanges.ExpiryChangeState.OK); + + assertThat(exp.getTimeToLive(TimeUnit.SECONDS)).satisfies(ttl -> { + assertThat(ttl.isPersistent()).isFalse(); + assertThat(ttl.value()).isGreaterThan(1); + }); + } + + @ParameterizedRedisTest // GH-3114 + @EnabledOnCommand("SPUBLISH") // Redis 7.0 + void testBoundExpireWithConditionsAndGetExpireSeconds() { + + K key = keyFactory.instance(); + V value1 = valueFactory.instance(); + redisTemplate.boundValueOps(key).set(value1); + + BoundKeyExpirationOperations exp = redisTemplate.expiration(key); + + assertThat(exp.expire(Duration.ofSeconds(5))).isEqualTo(ExpireChanges.ExpiryChangeState.OK); + assertThat(exp.expire(Expiration.from(Duration.ofSeconds(1)), ExpirationOptions.builder().gt().build())) + .isEqualTo(ExpireChanges.ExpiryChangeState.CONDITION_NOT_MET); + assertThat(exp.expire(Expiration.from(Duration.ofSeconds(10)), ExpirationOptions.builder().gt().build())) + .isEqualTo(ExpireChanges.ExpiryChangeState.OK); + + assertThat(exp.getTimeToLive(TimeUnit.SECONDS)).satisfies(ttl -> { + assertThat(ttl.isPersistent()).isFalse(); + assertThat(ttl.value()).isGreaterThan(5); + }); + } + @ParameterizedRedisTest void testGetExpireNoTimeUnit() { K key1 = keyFactory.instance(); diff --git a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapIntegrationTests.java b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapIntegrationTests.java index 31e93d06ac..f587339188 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/support/collections/AbstractRedisMapIntegrationTests.java @@ -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 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 @@ void testExpire() { V v1 = getValue(); assertThat(map.put(k1, v1)).isEqualTo(null); - BoundHashFieldExpirationOperations ops = map.expiration(Collections.singletonList(k1)); + BoundHashFieldExpirationOperations 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 @@ void testExpireAt() { V v1 = getValue(); assertThat(map.put(k1, v1)).isEqualTo(null); - BoundHashFieldExpirationOperations ops = map.expiration(Collections.singletonList(k1)); + BoundHashFieldExpirationOperations 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);