Skip to content

Commit

Permalink
Java: Add Time() command (valkey-io#1155)
Browse files Browse the repository at this point in the history
* Java: Add `Time()` command (#145)

* Add Time() command to Java client

Signed-off-by: Andrew Carbonetto <[email protected]>

---------

Signed-off-by: Andrew Carbonetto <[email protected]>

* Javadoc update

Signed-off-by: Andrew Carbonetto <[email protected]>

* Clean javadoc

Signed-off-by: Andrew Carbonetto <[email protected]>

* Remove warning in unit test for time()

Signed-off-by: Andrew Carbonetto <[email protected]>

---------

Signed-off-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
acarbonetto authored Mar 28, 2024
1 parent 81b8846 commit cae81c8
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 0 deletions.
8 changes: 8 additions & 0 deletions java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import static glide.utils.ArrayTransformUtils.castArray;
import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray;
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
import static redis_request.RedisRequestOuterClass.RequestType.ClientId;
Expand All @@ -13,6 +14,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Select;
import static redis_request.RedisRequestOuterClass.RequestType.Time;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.GenericCommands;
Expand Down Expand Up @@ -124,4 +126,10 @@ public CompletableFuture<String> echo(@NonNull String message) {
return commandManager.submitNewCommand(
Echo, new String[] {message}, this::handleStringResponse);
}

@Override
public CompletableFuture<String[]> time() {
return commandManager.submitNewCommand(
Time, new String[0], response -> castArray(handleArrayResponse(response), String.class));
}
}
22 changes: 22 additions & 0 deletions java/client/src/main/java/glide/api/RedisClusterClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import static glide.utils.ArrayTransformUtils.castArray;
import static glide.utils.ArrayTransformUtils.castMapOfArrays;
import static glide.utils.ArrayTransformUtils.convertMapToKeyValueStringArray;
import static redis_request.RedisRequestOuterClass.RequestType.ClientGetName;
import static redis_request.RedisRequestOuterClass.RequestType.ClientId;
Expand All @@ -12,6 +14,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Echo;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;

import glide.api.commands.ConnectionManagementClusterCommands;
import glide.api.commands.GenericClusterCommands;
Expand Down Expand Up @@ -262,4 +265,23 @@ public CompletableFuture<ClusterValue<String>> echo(
? ClusterValue.ofSingleValue(handleStringResponse(response))
: ClusterValue.ofMultiValue(handleMapResponse(response)));
}

@Override
public CompletableFuture<String[]> time() {
return commandManager.submitNewCommand(
Time, new String[0], response -> castArray(handleArrayResponse(response), String.class));
}

@Override
public CompletableFuture<ClusterValue<String[]>> time(@NonNull Route route) {
return commandManager.submitNewCommand(
Time,
new String[0],
route,
response ->
route.isSingleNodeRoute()
? ClusterValue.ofSingleValue(castArray(handleArrayResponse(response), String.class))
: ClusterValue.ofMultiValue(
castMapOfArrays(handleMapResponse(response), String.class)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,46 @@ public interface ServerManagementClusterCommands {
* }</pre>
*/
CompletableFuture<String> configSet(Map<String, String> parameters, Route route);

/**
* Returns the server time.<br>
* 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.
* @example
* <pre>{@code
* String[] serverTime = client.time().get();
* System.out.println("Server time is: " + serverTime[0] + "." + serverTime[1]);
* }</pre>
*/
CompletableFuture<String[]> time();

/**
* Returns the server time.
*
* @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.
* @example
* <pre>{@code
* // Command sent to a single random node via RANDOM route, expecting a SingleValue result.
* String[] serverTime = client.time().get(RANDOM).getSingleValue();
* System.out.println("Server time is: " + serverTime[0] + "." + serverTime[1]);
*
* // Command sent to all nodes via ALL_NODES route, expecting a MultiValue result.
* Map<String, String[]> serverTimeForAllNodes = client.time(ALL_NODES).get().getMultiValue();
* for(var serverTimePerNode : serverTimeForAllNodes.getMultiValue().entrySet()) {
* String node = serverTimePerNode.getKey();
* String serverTimePerNodeStr = serverTimePerNode.getValue()[0] + "." + serverTimePerNode.getValue()[1];
* System.out.println("Server time for node [" + node + "]: " + serverTimePerNodeStr);
* }
* }</pre>
*/
CompletableFuture<ClusterValue<String[]>> time(Route route);
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,19 @@ public interface ServerManagementCommands {
* }</pre>
*/
CompletableFuture<String> configSet(Map<String, String> parameters);

/**
* 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.
* @example
* <pre>{@code
* String[] serverTime = client.time().get();
* System.out.println("Server time is: " + serverTime[0] + "." + serverTime[1]);
* }</pre>
*/
CompletableFuture<String[]> time();
}
15 changes: 15 additions & 0 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
import static redis_request.RedisRequestOuterClass.RequestType.Type;
import static redis_request.RedisRequestOuterClass.RequestType.Unlink;
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
Expand Down Expand Up @@ -1288,6 +1289,20 @@ public T pttl(@NonNull String key) {
return getThis();
}

/**
* Returns the server time.
*
* @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]
* </code> format.
*/
public T time() {
protobufTransaction.addCommands(buildCommand(Time));
return getThis();
}

/**
* Returns the string representation of the type of the value stored at <code>key</code>.
*
Expand Down
17 changes: 17 additions & 0 deletions java/client/src/main/java/glide/utils/ArrayTransformUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** Utility methods for data conversion. */
Expand Down Expand Up @@ -51,6 +52,22 @@ public static <T, U extends T> U[] castArray(T[] objectArr, Class<U> clazz) {
.toArray(size -> (U[]) Array.newInstance(clazz, size));
}

/**
* Maps a Map of Arrays with value type T[] to value of U[].
*
* @param mapOfArrays Map of Array values to cast.
* @param clazz The class of the array values to cast to.
* @return A Map of arrays of type U[], containing the key/values from the input Map.
* @param <T> The base type from which the elements are being cast.
* @param <U> The subtype of T to which the elements are cast.
*/
@SuppressWarnings("unchecked")
public static <T, U extends T> Map<String, U[]> castMapOfArrays(
Map<String, T[]> mapOfArrays, Class<U> clazz) {
return mapOfArrays.entrySet().stream()
.collect(Collectors.toMap(k -> k.getKey(), e -> castArray(e.getValue(), clazz)));
}

/**
* Concatenates multiple arrays of type T and returns a single concatenated array.
*
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 @@ -64,6 +64,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
import static redis_request.RedisRequestOuterClass.RequestType.Type;
import static redis_request.RedisRequestOuterClass.RequestType.Unlink;
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
Expand Down Expand Up @@ -1731,4 +1732,24 @@ public void type_returns_success() {
assertEquals(testResponse, response);
assertEquals(value, payload);
}

@SneakyThrows
@Test
public void time_returns_success() {
// setup
CompletableFuture<String[]> testResponse = mock(CompletableFuture.class);
String[] payload = new String[] {"UnixTime", "ms"};
when(testResponse.get()).thenReturn(payload);

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

// exercise
CompletableFuture<String[]> response = service.time();

// verify
assertEquals(testResponse, response);
assertEquals(payload, response.get());
}
}
43 changes: 43 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.Echo;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;

import glide.api.models.ClusterValue;
import glide.api.models.commands.InfoOptions;
Expand Down Expand Up @@ -668,4 +669,46 @@ public void configSet_with_route_returns_success() {
assertEquals(testResponse, response);
assertEquals(OK, response.get());
}

@SneakyThrows
@Test
public void time_returns_success() {
// setup

String[] payload = new String[] {"UnixTime", "ms"};
CompletableFuture<String[]> testResponse = new CompletableFuture<>();
testResponse.complete(payload);

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

// exercise
CompletableFuture<String[]> response = service.time();

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

@SneakyThrows
@Test
public void time_returns_with_route_success() {
// setup
String[] payload = new String[] {"UnixTime", "ms"};
CompletableFuture<ClusterValue<String[]>> testResponse = new CompletableFuture<>();
testResponse.complete(ClusterValue.ofSingleValue(payload));

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

// exercise
CompletableFuture<ClusterValue<String[]>> response = service.time(RANDOM);

// verify
assertEquals(testResponse, response);
assertEquals(payload, response.get().getSingleValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.SetString;
import static redis_request.RedisRequestOuterClass.RequestType.Strlen;
import static redis_request.RedisRequestOuterClass.RequestType.TTL;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
import static redis_request.RedisRequestOuterClass.RequestType.Type;
import static redis_request.RedisRequestOuterClass.RequestType.Unlink;
import static redis_request.RedisRequestOuterClass.RequestType.Zadd;
Expand Down Expand Up @@ -385,6 +386,9 @@ public void transaction_builds_protobuf_request(BaseTransaction<?> transaction)
transaction.zcard("key");
results.add(Pair.of(Zcard, ArgsArray.newBuilder().addArgs("key").build()));

transaction.time();
results.add(Pair.of(Time, ArgsArray.newBuilder().build()));

transaction.type("key");
results.add(Pair.of(Type, ArgsArray.newBuilder().addArgs("key").build()));

Expand Down
35 changes: 35 additions & 0 deletions java/integTest/src/test/java/glide/cluster/CommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import glide.api.models.configuration.RequestRoutingConfiguration.SlotKeyRoute;
import glide.api.models.exceptions.RedisException;
import glide.api.models.exceptions.RequestException;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -523,4 +524,38 @@ public void echo_with_route() {
Map<String, String> multiPayload = clusterClient.echo(message, ALL_NODES).get().getMultiValue();
multiPayload.forEach((key, value) -> assertEquals(message, value));
}

@Test
@SneakyThrows
public void time() {
// Take the time now, convert to 10 digits and subtract 1 second
long now = Instant.now().getEpochSecond() - 1L;
String[] result = clusterClient.time().get();
assertEquals(2, result.length);
assertTrue(
Long.parseLong(result[0]) > now,
"Time() result (" + result[0] + ") should be greater than now (" + now + ")");
assertTrue(Long.parseLong(result[1]) < 1000000);
}

@Test
@SneakyThrows
public void time_with_route() {
// Take the time now, convert to 10 digits and subtract 1 second
long now = Instant.now().getEpochSecond() - 1L;

ClusterValue<String[]> result = clusterClient.time(ALL_PRIMARIES).get();
assertTrue(result.hasMultiData());
assertTrue(result.getMultiValue().size() > 1);

// check the first node's server time
Object[] serverTime =
result.getMultiValue().get(result.getMultiValue().keySet().toArray(String[]::new)[0]);

assertEquals(2, serverTime.length);
assertTrue(
Long.parseLong((String) serverTime[0]) > now,
"Time() result (" + serverTime[0] + ") should be greater than now (" + now + ")");
assertTrue(Long.parseLong((String) serverTime[1]) < 1000000);
}
}
16 changes: 16 additions & 0 deletions java/integTest/src/test/java/glide/standalone/CommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import glide.api.models.configuration.NodeAddress;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.api.models.exceptions.RequestException;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -250,4 +251,19 @@ public void echo() {
String response = regularClient.echo(message).get();
assertEquals(message, response);
}

@Test
@SneakyThrows
public void time() {
// Take the time now, convert to 10 digits and subtract 1 second
long now = Instant.now().getEpochSecond() - 1L;
String[] result = regularClient.time().get();

assertEquals(2, result.length);

assertTrue(
Long.parseLong(result[0]) > now,
"Time() result (" + result[0] + ") should be greater than now (" + now + ")");
assertTrue(Long.parseLong(result[1]) < 1000000);
}
}

0 comments on commit cae81c8

Please sign in to comment.