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

Add GET/SET/INFO/PING commands #66

Closed
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
55 changes: 55 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package glide.api;

import glide.api.models.exceptions.RedisException;
import glide.ffi.resolvers.RedisValueResolver;
import glide.managers.BaseCommandResponseResolver;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import lombok.AllArgsConstructor;
import response.ResponseOuterClass.Response;
Expand All @@ -28,6 +30,59 @@ protected static Object handleObjectResponse(Response response) {
return (new BaseCommandResponseResolver(RedisValueResolver::valueFromPointer)).apply(response);
}

/**
* Check for errors in the Response and return null Throws an error if an unexpected value is
* returned
*
* @return null if the response is empty
Comment on lines +34 to +37

Choose a reason for hiding this comment

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

Should we use the @throws tag here?

Suggested change
* Check for errors in the Response and return null Throws an error if an unexpected value is
* returned
*
* @return null if the response is empty
* Check for errors in the Response and return null.
*
* @return null if the response is empty
* @throws RedisException if an unexpected (non-null) value is returned

Choose a reason for hiding this comment

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

+1

*/
protected static Void handleVoidResponse(Response response) {
Object value = handleObjectResponse(response);
if (value == null) {
return null;
}
throw new RedisException(
"Unexpected return type from Redis: got "
+ value.getClass().getSimpleName()
+ " expected null");
}

/**
* Extracts the response value from the Redis response and either throws an exception or returns
* the value as a String.
*
* @param response Redis protobuf message
* @return Response as a String
*/
protected static String handleStringResponse(Response response) {
Object value = handleObjectResponse(response);
if (value instanceof String) {
return (String) value;
}
throw new RedisException(
"Unexpected return type from Redis: got "
+ value.getClass().getSimpleName()
+ " expected String");
}

/**
* Extracts the response value from the Redis response and either throws an exception or returns
* the * value as a HashMap
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
*
* @param response Redis protobuf message
* @return Response as a String
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
*/
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
protected static HashMap<String, Object> handleMapResponse(Response response) {
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
Object value = handleObjectResponse(response);
if (value instanceof HashMap) {
return (HashMap<String, Object>) value;
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
}
throw new RedisException(
"Unexpected return type from Redis: got "
+ value.getClass().getSimpleName()
+ " expected HashMap");
}

/**
* Closes this resource, relinquishing any underlying resources. This method is invoked
* automatically on objects managed by the try-with-resources statement.
Expand Down
3 changes: 3 additions & 0 deletions java/client/src/main/java/glide/api/ClusterClient.java
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package glide.api;

public class ClusterClient {}
125 changes: 124 additions & 1 deletion java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@
import static glide.ffi.resolvers.SocketListenerResolver.getSocket;

import glide.api.commands.BaseCommands;
import glide.api.commands.ConnectionCommands;
import glide.api.commands.GenericCommands;
import glide.api.commands.ServerCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.InfoOptions;
import glide.api.models.commands.SetOptions;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.connectors.handlers.CallbackDispatcher;
import glide.connectors.handlers.ChannelHandler;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import glide.managers.models.Command;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
* Async (non-blocking) client for Redis in Standalone mode. Use {@link
* #CreateClient(RedisClientConfiguration)} to request a client to Redis.
*/
public class RedisClient extends BaseClient implements BaseCommands {
public class RedisClient extends BaseClient
implements BaseCommands, GenericCommands, ConnectionCommands, ServerCommands, StringCommands {

/**
* Request an async (non-blocking) Redis client in Standalone mode.
Expand Down Expand Up @@ -78,4 +87,118 @@ public CompletableFuture<Object> customCommand(String[] args) {
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
return commandManager.submitNewCommand(command, BaseClient::handleObjectResponse);
}

/**
* Ping the Redis server.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
* @returns the String "PONG"

Choose a reason for hiding this comment

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

Would it be preferable to specify that the method does not return a string but instead a completable future that resolves to a string? Same question for the other javadocs in this file. Also, note that javadocs use @return instead of @returns

Suggested change
* @returns the String "PONG"
* @return a completable future that returns the String "PONG"

Choose a reason for hiding this comment

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

Suggested change
* @returns the String "PONG"
* @return A completable future that returns server response

Server implementation maybe changed

*/
@Override
public CompletableFuture<String> ping() {
Command command = Command.builder().requestType(Command.RequestType.PING).build();
return commandManager.submitNewCommand(command, BaseClient::handleStringResponse);
}

/**
* Ping the Redis server.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
* @param msg - the ping argument that will be returned.
* @returns return a copy of the argument.
Copy link

@aaron-congo aaron-congo Jan 26, 2024

Choose a reason for hiding this comment

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

Suggested change
* @returns return a copy of the argument.
* @return A copy of the argument.

Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
@Override
public CompletableFuture<String> ping(String msg) {
Command command =
Command.builder()
.requestType(Command.RequestType.PING)
.arguments(new String[] {msg})
.build();
return commandManager.submitNewCommand(command, BaseClient::handleStringResponse);
}

/**
* Get information and statistics about the Redis server. DEFAULT option is assumed
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @see <a href="https://redis.io/commands/info/">redis.io</a> for details.
* @return CompletableFuture with the response
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
@Override
public CompletableFuture<Map> info() {
Command command = Command.builder().requestType(Command.RequestType.INFO).build();
return commandManager.submitNewCommand(command, BaseClient::handleMapResponse);
}

/**
* Get information and statistics about the Redis server.
*
* @see <a href="https://redis.io/commands/info/">redis.io</a> for details.
* @param options - A list of InfoSection values specifying which sections of information to
* retrieve. When no parameter is provided, the default option is assumed.
* @return CompletableFuture with the response
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
@Override
public CompletableFuture<Map> info(InfoOptions options) {
Command command =
Command.builder()
.requestType(Command.RequestType.INFO)
.arguments(options.toInfoOptions())
.build();
return commandManager.submitNewCommand(command, BaseClient::handleMapResponse);
}

/**
* Get the value associated with the given key, or null if no such value exists.
*
* @see <a href="https://redis.io/commands/get/">redis.io</a> for details.
* @param key - The key to retrieve from the database.
* @return If `key` exists, returns the value of `key` as a string. Otherwise, return null
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
@Override
public CompletableFuture<String> get(String key) {
Command command =
Command.builder()
.requestType(Command.RequestType.GET_STRING)
.arguments(new String[] {key})
.build();
return commandManager.submitNewCommand(command, BaseClient::handleStringResponse);
}

/**
* Set the given key with the given value.
*
* @see <a href="https://redis.io/commands/set/">redis.io</a> for details.
* @param key - The key to store.
* @param value - The value to store with the given key.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @return null
Copy link

@aaron-congo aaron-congo Jan 26, 2024

Choose a reason for hiding this comment

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

nit:

Suggested change
* @return null
* @return A CompletableFuture that can be used to wait for the command to complete. Set has no return value, so the CompletableFuture will return null.

Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
@Override
public CompletableFuture<Void> set(String key, String value) {
Command command =
Command.builder()
.requestType(Command.RequestType.SET_STRING)
.arguments(new String[] {key, value})
.build();
return commandManager.submitNewCommand(command, BaseClient::handleVoidResponse);
}

/**
* Set the given key with the given value. Return value is dependent on the passed options.
*
* @see <a href="https://redis.io/commands/set/">redis.io</a> for details.
* @param key - The key to store.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* @param value - The value to store with the given key.
* @param options - The Set options
* @return string or null If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist`
* conditions, return null. If `returnOldValue` is set, return the old value as a string.
Comment on lines +192 to +193
Copy link

@aaron-congo aaron-congo Jan 26, 2024

Choose a reason for hiding this comment

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

Taking from the python docs:

Suggested change
* @return string or null If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist`
* conditions, return null. If `returnOldValue` is set, return the old value as a string.
* @return The old value as a string of `returnOldValue` is set. Otherwise, if the value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist` conditions, return null. Otherwise, return "OK".

*/
@Override
public CompletableFuture<String> set(String key, String value, SetOptions options) {
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
Command command =
Command.builder()
.requestType(Command.RequestType.SET_STRING)
.arguments(options.toSetOptions(List.of(key, value)))
.build();
return commandManager.submitNewCommand(command, BaseClient::handleStringResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package glide.api.commands;

import java.util.concurrent.CompletableFuture;

/**
* Connection Management Commands interface.
*
* @see: <a href="https://redis.io/commands/?group=connection">Server Management Commands</a>
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
public interface ConnectionCommands {

CompletableFuture<String> ping();
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved

CompletableFuture<String> ping(String msg);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package glide.api.commands;

/**
* Generic Commands interface to handle 'general' commands.
*
* @see: <a href="https://redis.io/commands/?group=generic">General Commands</a>
*/
public interface GenericCommands {}
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 17 additions & 0 deletions java/client/src/main/java/glide/api/commands/ServerCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package glide.api.commands;

import glide.api.models.commands.InfoOptions;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
* Server Management Commands interface.
*
* @see: <a href="https://redis.io/commands/?group=server">Server Management Commands</a>
*/
public interface ServerCommands {

CompletableFuture<Map> info();
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved

CompletableFuture<Map> info(InfoOptions options);
}
14 changes: 14 additions & 0 deletions java/client/src/main/java/glide/api/commands/StringCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package glide.api.commands;

import glide.api.models.commands.SetOptions;
import java.util.concurrent.CompletableFuture;

/** String Commands interface to handle single commands that return Strings. */
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
public interface StringCommands {

CompletableFuture<String> get(String key);

CompletableFuture<Void> set(String key, String value);

CompletableFuture<String> set(String key, String value, SetOptions options);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package glide.api.models.commands;

import glide.api.commands.ServerCommands;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.Singular;

/** Object builder to add optional arguments to {@link ServerCommands#info(InfoOptions)} */
@Builder
public class InfoOptions extends Options {

@Singular private final List<Section> sections;

public enum Section {
/** SERVER: General information about the Redis server */
SERVER,
/** CLIENTS: Client connections section */
CLIENTS,
/** MEMORY: Memory consumption related information */
MEMORY,
/** PERSISTENCE: RDB and AOF related information */
PERSISTENCE,
/** STATS: General statistics */
STATS,
/** REPLICATION: Master/replica replication information */
REPLICATION,
/** CPU: CPU consumption statistics */
CPU,
/** COMMANDSTATS: Redis command statistics */
COMMANDSTATS,
/** LATENCYSTATS: Redis command latency percentile distribution statistics */
LATENCYSTATS,
/** SENTINEL: Redis Sentinel section (only applicable to Sentinel instances) */
SENTINEL,
/** CLUSTER: Redis Cluster section */
CLUSTER,
/** MODULES: Modules section */
MODULES,
/** KEYSPACE: Database related statistics */
KEYSPACE,
/** ERRORSTATS: Redis error statistics */
ERRORSTATS,
/** ALL: Return all sections (excluding module generated ones) */
ALL,
/** DEFAULT: Return only the default set of sections */
DEFAULT,
/** EVERYTHING: Includes all and modules */
EVERYTHING,
}

/**
* Converts options enum into a String[] to add to a {@link glide.api.models.Command}
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*
* @return String[]
Copy link

@aaron-congo aaron-congo Jan 26, 2024

Choose a reason for hiding this comment

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

Suggested change
* @return String[]
* @return The sections held by this InfoOptions as an array of strings

*/
public String[] toInfoOptions() {
optionArgs = sections.stream().map(Objects::toString).collect(Collectors.toList());
return toArgs();
}
}
20 changes: 20 additions & 0 deletions java/client/src/main/java/glide/api/models/commands/Options.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package glide.api.models.commands;

import java.util.LinkedList;
import java.util.List;

/** Options base object to Options to a {@link glide.api.models.Command} */
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
public abstract class Options {
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved

protected List optionArgs;
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved

public String[] toArgs() {
return toArgs(List.of());
}

public String[] toArgs(List<String> arguments) {
List<String> args = new LinkedList<>(arguments);
args.addAll(optionArgs);
return args.toArray(new String[0]);
}
}
Loading
Loading