Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Add LASTSAVE command. #1297

Merged
merged 5 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this should be camel-case or left all lower-case?

Suggested change
public CompletableFuture<Long> lastsave() {
public CompletableFuture<Long> lastSave() {

Copy link
Collaborator Author

@Yury-Fridlyand Yury-Fridlyand Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All commands are in lowercase, except two-word commands like clientId, etc

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
Loading