Skip to content

Commit

Permalink
Java: Add LASTSAVE command. (valkey-io#1297)
Browse files Browse the repository at this point in the history
* Add `LASTSAVE` command. (#222)

Signed-off-by: Yury-Fridlyand <[email protected]>
Co-authored-by: Aaron <[email protected]>
  • Loading branch information
Yury-Fridlyand and aaron-congo authored Apr 17, 2024
1 parent 3b29627 commit 5d8548c
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 13 deletions.
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ enum RequestType {
GetRange = 116;
SMove = 117;
SMIsMember = 118;
LastSave = 120;
}

message Command {
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub enum RequestType {
GetRange = 116,
SMove = 117,
SMIsMember = 118,
LastSave = 120,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -257,6 +258,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::GetRange => RequestType::GetRange,
ProtobufRequestType::SMove => RequestType::SMove,
ProtobufRequestType::SMIsMember => RequestType::SMIsMember,
ProtobufRequestType::LastSave => RequestType::LastSave,
}
}
}
Expand Down Expand Up @@ -383,6 +385,7 @@ impl RequestType {
RequestType::GetRange => Some(cmd("GETRANGE")),
RequestType::SMove => Some(cmd("SMOVE")),
RequestType::SMIsMember => Some(cmd("SMISMEMBER")),
RequestType::LastSave => Some(cmd("LASTSAVE")),
}
}
}
6 changes: 6 additions & 0 deletions java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.Echo;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Select;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
Expand Down Expand Up @@ -132,4 +133,9 @@ public CompletableFuture<String[]> time() {
return commandManager.submitNewCommand(
Time, new String[0], response -> castArray(handleArrayResponse(response), String.class));
}

@Override
public CompletableFuture<Long> lastsave() {
return commandManager.submitNewCommand(LastSave, new String[0], this::handleLongResponse);
}
}
18 changes: 18 additions & 0 deletions java/client/src/main/java/glide/api/RedisClusterClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.Echo;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;

Expand Down Expand Up @@ -279,4 +280,21 @@ public CompletableFuture<ClusterValue<String[]>> time(@NonNull Route route) {
: ClusterValue.ofMultiValue(
castMapOfArrays(handleMapResponse(response), String.class)));
}

@Override
public CompletableFuture<Long> lastsave() {
return commandManager.submitNewCommand(LastSave, new String[0], this::handleLongResponse);
}

@Override
public CompletableFuture<ClusterValue<Long>> lastsave(@NonNull Route route) {
return commandManager.submitNewCommand(
LastSave,
new String[0],
route,
response ->
route instanceof SingleNodeRoute
? ClusterValue.of(handleLongResponse(response))
: ClusterValue.of(handleMapResponse(response)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,9 @@ public interface ServerManagementClusterCommands {
* The command will be routed to a random node.
*
* @see <a href="https://redis.io/commands/time/">redis.io</a> for details.
* @return The current server time as a <code>String</code> array with two elements: A Unix
* timestamp and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[Unix timestamp, Microseconds already elapsed]</code> format.
* @return The current server time as a <code>String</code> array with two elements: A <code>
* UNIX TIME</code> and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[UNIX TIME, Microseconds already elapsed]</code> format.
* @example
* <pre>{@code
* String[] serverTime = client.time().get();
Expand All @@ -263,9 +263,9 @@ public interface ServerManagementClusterCommands {
* @see <a href="https://redis.io/commands/time/">redis.io</a> for details.
* @param route Specifies the routing configuration for the command. The client will route the
* command to the nodes defined by <code>route</code>.
* @return The current server time as a <code>String</code> array with two elements: A Unix
* timestamp and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[Unix timestamp, Microseconds already elapsed]</code> format.
* @return The current server time as a <code>String</code> array with two elements: A <code>
* UNIX TIME</code> and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[UNIX TIME, Microseconds already elapsed]</code> format.
* @example
* <pre>{@code
* // Command sent to a single random node via RANDOM route, expecting a SingleValue result.
Expand All @@ -282,4 +282,37 @@ public interface ServerManagementClusterCommands {
* }</pre>
*/
CompletableFuture<ClusterValue<String[]>> time(Route route);

/**
* Returns <code>UNIX TIME</code> of the last DB save timestamp or startup timestamp if no save
* was made since then.<br>
* The command will be routed to a random node.
*
* @see <a href="https://redis.io/commands/lastsave/">redis.io</a> for details.
* @return <code>UNIX TIME</code> of the last DB save executed with success.
* @example
* <pre>{@code
* Long timestamp = client.lastsave().get();
* System.out.printf("Last DB save was done at %s%n", Instant.ofEpochSecond(timestamp));
* }</pre>
*/
CompletableFuture<Long> lastsave();

/**
* Returns <code>UNIX TIME</code> of the last DB save timestamp or startup timestamp if no save
* was made since then.
*
* @see <a href="https://redis.io/commands/lastsave/">redis.io</a> for details.
* @param route Specifies the routing configuration for the command. The client will route the
* command to the nodes defined by <code>route</code>.
* @return <code>UNIX TIME</code> of the last DB save executed with success.
* @example
* <pre>{@code
* ClusterValue<Long> data = client.lastsave(ALL_NODES).get();
* for (Map.Entry<String, Long> entry : data.getMultiValue().entrySet()) {
* System.out.printf("Last DB save on node %s was made at %s%n", entry.getKey(), Instant.ofEpochSecond(entry.getValue()));
* }
* }</pre>
*/
CompletableFuture<ClusterValue<Long>> lastsave(Route route);
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,28 @@ public interface ServerManagementCommands {
* Returns the server time.
*
* @see <a href="https://redis.io/commands/time/">redis.io</a> for details.
* @return The current server time as a <code>String</code> array with two elements: A Unix
* timestamp and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[Unix timestamp, Microseconds already elapsed]</code> format.
* @return The current server time as a <code>String</code> array with two elements: A <code>
* UNIX TIME</code> and the amount of microseconds already elapsed in the current second. The
* returned array is in a <code>[UNIX TIME, Microseconds already elapsed]</code> format.
* @example
* <pre>{@code
* String[] serverTime = client.time().get();
* System.out.println("Server time is: " + serverTime[0] + "." + serverTime[1]);
* }</pre>
*/
CompletableFuture<String[]> time();

/**
* Returns <code>UNIX TIME</code> of the last DB save timestamp or startup timestamp if no save
* was made since then.
*
* @see <a href="https://redis.io/commands/lastsave/">redis.io</a> for details.
* @return <code>UNIX TIME</code> of the last DB save executed with success.
* @example
* <pre>{@code
* Long timestamp = client.lastsave().get();
* System.out.printf("Last DB save was done at %s%n", Instant.ofEpochSecond(timestamp));
* }</pre>
*/
CompletableFuture<Long> lastsave();
}
17 changes: 15 additions & 2 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.LRange;
import static redis_request.RedisRequestOuterClass.RequestType.LRem;
import static redis_request.RedisRequestOuterClass.RequestType.LTrim;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Lindex;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
Expand Down Expand Up @@ -1938,15 +1939,27 @@ public T persist(@NonNull String key) {
*
* @see <a href="https://redis.io/commands/time/">redis.io</a> for details.
* @return Command Response - The current server time as a <code>String</code> array with two
* elements: A Unix timestamp and the amount of microseconds already elapsed in the current
* second. The returned array is in a <code>[Unix timestamp, Microseconds already elapsed]
* elements: A <code>UNIX TIME</code> and the amount of microseconds already elapsed in the
* current second. The returned array is in a <code>[UNIX TIME, Microseconds already elapsed]
* </code> format.
*/
public T time() {
protobufTransaction.addCommands(buildCommand(Time));
return getThis();
}

/**
* Returns <code>UNIX TIME</code> of the last DB save timestamp or startup timestamp if no save
* was made since then.
*
* @see <a href="https://redis.io/commands/lastsave/">redis.io</a> for details.
* @return Command Response - <code>UNIX TIME</code> of the last DB save executed with success.
*/
public T lastsave() {
protobufTransaction.addCommands(buildCommand(LastSave));
return getThis();
}

/**
* Returns the string representation of the type of the value stored at <code>key</code>.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public static Expiry Milliseconds(Long milliseconds) {
* Set the specified Unix time at which the key will expire, in seconds. Equivalent to <code>
* EXAT</code> in the Redis API.
*
* @param unixSeconds unix time to expire, in seconds
* @param unixSeconds <code>UNIX TIME</code> to expire, in seconds.
* @return Expiry
*/
public static Expiry UnixSeconds(Long unixSeconds) {
Expand All @@ -119,7 +119,7 @@ public static Expiry UnixSeconds(Long unixSeconds) {
* Set the specified Unix time at which the key will expire, in milliseconds. Equivalent to
* <code>PXAT</code> in the Redis API.
*
* @param unixMilliseconds unix time to expire, in milliseconds
* @param unixMilliseconds <code>UNIX TIME</code> to expire, in milliseconds.
* @return Expiry
*/
public static Expiry UnixMilliseconds(Long unixMilliseconds) {
Expand Down
21 changes: 21 additions & 0 deletions java/client/src/test/java/glide/api/RedisClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.LRange;
import static redis_request.RedisRequestOuterClass.RequestType.LRem;
import static redis_request.RedisRequestOuterClass.RequestType.LTrim;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Lindex;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
Expand Down Expand Up @@ -3040,6 +3041,26 @@ public void time_returns_success() {
assertEquals(payload, response.get());
}

@SneakyThrows
@Test
public void lastsave_returns_success() {
// setup
Long value = 42L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(LastSave), eq(new String[0]), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.lastsave();

// verify
assertEquals(testResponse, response);
assertEquals(value, response.get());
}

@SneakyThrows
@Test
public void linsert_returns_success() {
Expand Down
42 changes: 42 additions & 0 deletions java/client/src/test/java/glide/api/RedisClusterClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.ConfigSet;
import static redis_request.RedisRequestOuterClass.RequestType.Echo;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;

Expand Down Expand Up @@ -771,4 +772,45 @@ public void time_returns_with_route_success() {
assertEquals(testResponse, response);
assertEquals(payload, response.get().getSingleValue());
}

@SneakyThrows
@Test
public void lastsave_returns_success() {
// setup
Long value = 42L;
CompletableFuture<Long> testResponse = new CompletableFuture<>();
testResponse.complete(value);

// match on protobuf request
when(commandManager.<Long>submitNewCommand(eq(LastSave), eq(new String[0]), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<Long> response = service.lastsave();

// verify
assertEquals(testResponse, response);
assertEquals(value, response.get());
}

@SneakyThrows
@Test
public void lastsave_returns_with_route_success() {
// setup
Long value = 42L;
CompletableFuture<ClusterValue<Long>> testResponse = new CompletableFuture<>();
testResponse.complete(ClusterValue.ofSingleValue(value));

// match on protobuf request
when(commandManager.<ClusterValue<Long>>submitNewCommand(
eq(LastSave), eq(new String[0]), eq(RANDOM), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<ClusterValue<Long>> response = service.lastsave(RANDOM);

// verify
assertEquals(testResponse, response);
assertEquals(value, response.get().getSingleValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.LRange;
import static redis_request.RedisRequestOuterClass.RequestType.LRem;
import static redis_request.RedisRequestOuterClass.RequestType.LTrim;
import static redis_request.RedisRequestOuterClass.RequestType.LastSave;
import static redis_request.RedisRequestOuterClass.RequestType.Lindex;
import static redis_request.RedisRequestOuterClass.RequestType.MGet;
import static redis_request.RedisRequestOuterClass.RequestType.MSet;
Expand Down Expand Up @@ -451,6 +452,9 @@ InfScoreBound.NEGATIVE_INFINITY, new ScoreBoundary(3, false), new Limit(1, 2)),
transaction.time();
results.add(Pair.of(Time, buildArgs()));

transaction.lastsave();
results.add(Pair.of(LastSave, buildArgs()));

transaction.persist("key");
results.add(Pair.of(Persist, buildArgs("key")));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import glide.api.models.ClusterTransaction;
import glide.api.models.configuration.NodeAddress;
import glide.api.models.configuration.RedisClusterClientConfiguration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -82,4 +84,12 @@ public void test_cluster_transactions() {
Object[] results = clusterClient.exec(transaction, RANDOM).get();
assertArrayEquals(expectedResult, results);
}

@Test
@SneakyThrows
public void lastsave() {
var yesterday = Instant.now().minus(1, ChronoUnit.DAYS);
var response = clusterClient.exec(new ClusterTransaction().lastsave()).get();
assertTrue(Instant.ofEpochSecond((long) response[0]).isAfter(yesterday));
}
}
15 changes: 15 additions & 0 deletions java/integTest/src/test/java/glide/cluster/CommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import glide.api.models.exceptions.RedisException;
import glide.api.models.exceptions.RequestException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -556,4 +557,18 @@ public void time_with_route() {
"Time() result (" + serverTime[0] + ") should be greater than now (" + now + ")");
assertTrue(Long.parseLong((String) serverTime[1]) < 1000000);
}

@Test
@SneakyThrows
public void lastsave() {
long result = clusterClient.lastsave().get();
var yesterday = Instant.now().minus(1, ChronoUnit.DAYS);

assertTrue(Instant.ofEpochSecond(result).isAfter(yesterday));

ClusterValue<Long> data = clusterClient.lastsave(ALL_NODES).get();
for (var value : data.getMultiValue().values()) {
assertTrue(Instant.ofEpochSecond(value).isAfter(yesterday));
}
}
}
Loading

0 comments on commit 5d8548c

Please sign in to comment.