Skip to content

Commit b062b9b

Browse files
committed
Add transactions for single and multi-cluster clients
Signed-off-by: Andrew Carbonetto <[email protected]>
1 parent 4d2b723 commit b062b9b

18 files changed

+1254
-59
lines changed
Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,26 @@
11
package glide.api;
22

3+
import glide.api.commands.BaseCommands;
4+
import glide.api.commands.Transaction;
5+
import glide.api.models.exceptions.RedisException;
36
import glide.ffi.resolvers.RedisValueResolver;
47
import glide.managers.BaseCommandResponseResolver;
58
import glide.managers.CommandManager;
69
import glide.managers.ConnectionManager;
10+
import glide.managers.models.Command;
11+
import java.util.HashMap;
12+
import java.util.concurrent.CompletableFuture;
713
import java.util.concurrent.ExecutionException;
814
import lombok.AllArgsConstructor;
915
import response.ResponseOuterClass.Response;
1016

1117
/** Base Client class for Redis */
1218
@AllArgsConstructor
13-
public abstract class BaseClient implements AutoCloseable {
19+
public abstract class BaseClient implements AutoCloseable, BaseCommands {
1420

1521
protected final ConnectionManager connectionManager;
1622
protected final CommandManager commandManager;
1723

18-
/**
19-
* Extracts the response from the Protobuf response and either throws an exception or returns the
20-
* appropriate response as an Object
21-
*
22-
* @param response Redis protobuf message
23-
* @return Response Object
24-
*/
25-
protected Object handleObjectResponse(Response response) {
26-
// convert protobuf response into Object and then Object into T
27-
return new BaseCommandResponseResolver(RedisValueResolver::valueFromPointer).apply(response);
28-
}
29-
3024
/**
3125
* Closes this resource, relinquishing any underlying resources. This method is invoked
3226
* automatically on objects managed by the try-with-resources statement.
@@ -43,4 +37,111 @@ public void close() throws ExecutionException {
4337
throw new RuntimeException(e);
4438
}
4539
}
40+
41+
/**
42+
* Extracts the response from the Protobuf response and either throws an exception or returns the
43+
* appropriate response as an Object
44+
*
45+
* @param response Redis protobuf message
46+
* @return Response Object
47+
*/
48+
protected static Object handleObjectResponse(Response response) {
49+
// convert protobuf response into Object and then Object into T
50+
return new BaseCommandResponseResolver(RedisValueResolver::valueFromPointer).apply(response);
51+
}
52+
53+
/**
54+
* Check for errors in the Response and return null Throws an error if an unexpected value is
55+
* returned
56+
*
57+
* @return null if the response is empty
58+
*/
59+
protected static Void handleVoidResponse(Response response) {
60+
Object value = handleObjectResponse(response);
61+
if (value == null) {
62+
return null;
63+
}
64+
throw new RedisException(
65+
"Unexpected return type from Redis: got "
66+
+ value.getClass().getSimpleName()
67+
+ " expected null");
68+
}
69+
70+
/**
71+
* Extracts the response value from the Redis response and either throws an exception or returns
72+
* the value as a String.
73+
*
74+
* @param response Redis protobuf message
75+
* @return Response as a String
76+
*/
77+
protected static String handleStringResponse(Response response) {
78+
Object value = handleObjectResponse(response);
79+
if (value instanceof String) {
80+
return (String) value;
81+
}
82+
throw new RedisException(
83+
"Unexpected return type from Redis: got "
84+
+ value.getClass().getSimpleName()
85+
+ " expected String");
86+
}
87+
88+
/**
89+
* Extracts the response value from the Redis response and either throws an exception or returns
90+
* the value as an Object[].
91+
*
92+
* @param response Redis protobuf message
93+
* @return Response as an Object[]
94+
*/
95+
protected static Object[] handleArrayResponse(Response response) {
96+
Object value = handleObjectResponse(response);
97+
if (value instanceof Object[]) {
98+
return (Object[]) value;
99+
}
100+
throw new RedisException(
101+
"Unexpected return type from Redis: got "
102+
+ value.getClass().getSimpleName()
103+
+ " expected Object[]");
104+
}
105+
106+
/**
107+
* Extracts the response value from the Redis response and either throws an exception or returns
108+
* the * value as a HashMap
109+
*
110+
* @param response Redis protobuf message
111+
* @return Response as a String
112+
*/
113+
protected static HashMap<String, Object> handleMapResponse(Response response) {
114+
Object value = handleObjectResponse(response);
115+
if (value instanceof HashMap) {
116+
return (HashMap<String, Object>) value;
117+
}
118+
throw new RedisException(
119+
"Unexpected return type from Redis: got "
120+
+ value.getClass().getSimpleName()
121+
+ " expected HashMap");
122+
}
123+
124+
@Override
125+
public CompletableFuture<Object> customCommand(String[] args) {
126+
Command command =
127+
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
128+
return commandManager.submitNewCommand(command, BaseClient::handleObjectResponse);
129+
}
130+
131+
/**
132+
* Execute a transaction by processing the queued commands.
133+
*
134+
* @see <a href="https://redis.io/topics/Transactions/">redis.io</a> for details on Redis
135+
* Transactions.
136+
* @param transaction - A {@link Transaction} object containing a list of commands to be executed.
137+
* @return A list of results corresponding to the execution of each command in the transaction.
138+
* <ul>
139+
* <li>If a command returns a value, it will be included in the list. If a command doesn't
140+
* return a value, the list entry will be null.
141+
* <li>If the transaction failed due to a WATCH command, `exec` will return `null`.
142+
* </ul>
143+
*/
144+
public CompletableFuture<Object[]> exec(Transaction transaction) {
145+
return commandManager.submitNewTransaction(transaction, BaseClient::handleArrayResponse);
146+
}
46147
}

java/client/src/main/java/glide/api/ClusterClient.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* Async (non-blocking) client for Redis in Cluster mode. Use {@link #CreateClient} to request a
1919
* client to Redis.
2020
*/
21-
public class ClusterClient extends BaseClient implements ClusterBaseCommands<ClusterValue<Object>> {
21+
public class ClusterClient extends BaseClient implements ClusterBaseCommands {
2222

2323
protected ClusterClient(ConnectionManager connectionManager, CommandManager commandManager) {
2424
super(connectionManager, commandManager);
@@ -48,14 +48,6 @@ public static CompletableFuture<ClusterClient> CreateClient(
4848
}
4949
}
5050

51-
@Override
52-
public CompletableFuture<ClusterValue<Object>> customCommand(String[] args) {
53-
Command command =
54-
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
55-
return commandManager.submitNewCommand(
56-
command, response -> ClusterValue.of(handleObjectResponse(response)));
57-
}
58-
5951
@Override
6052
public CompletableFuture<ClusterValue<Object>> customCommand(String[] args, Route route) {
6153
Command command =

java/client/src/main/java/glide/api/RedisClient.java

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@
22

33
import static glide.ffi.resolvers.SocketListenerResolver.getSocket;
44

5-
import glide.api.commands.BaseCommands;
5+
import glide.api.commands.ConnectionCommands;
6+
import glide.api.commands.GenericCommands;
7+
import glide.api.commands.ServerCommands;
8+
import glide.api.commands.StringCommands;
9+
import glide.api.models.commands.InfoOptions;
10+
import glide.api.models.commands.SetOptions;
611
import glide.api.models.configuration.RedisClientConfiguration;
712
import glide.connectors.handlers.CallbackDispatcher;
813
import glide.connectors.handlers.ChannelHandler;
914
import glide.managers.CommandManager;
1015
import glide.managers.ConnectionManager;
1116
import glide.managers.models.Command;
17+
import java.util.Map;
1218
import java.util.concurrent.CompletableFuture;
1319

1420
/**
1521
* Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient} to request a
1622
* client to Redis.
1723
*/
18-
public class RedisClient extends BaseClient implements BaseCommands<Object> {
24+
public class RedisClient extends BaseClient
25+
implements ConnectionCommands, GenericCommands, ServerCommands, StringCommands {
1926

2027
protected RedisClient(ConnectionManager connectionManager, CommandManager commandManager) {
2128
super(connectionManager, commandManager);
@@ -57,10 +64,92 @@ protected static CommandManager buildCommandManager(ChannelHandler channelHandle
5764
return new CommandManager(channelHandler);
5865
}
5966

67+
/**
68+
* Ping the Redis server.
69+
*
70+
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
71+
* @return the String "PONG"
72+
*/
73+
@Override
74+
public CompletableFuture<String> ping() {
75+
return commandManager.submitNewCommand(Command.ping(), BaseClient::handleStringResponse);
76+
}
77+
78+
/**
79+
* Ping the Redis server.
80+
*
81+
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
82+
* @param msg - the ping argument that will be returned.
83+
* @return return a copy of the argument.
84+
*/
85+
@Override
86+
public CompletableFuture<String> ping(String msg) {
87+
return commandManager.submitNewCommand(Command.ping(msg), BaseClient::handleStringResponse);
88+
}
89+
90+
/**
91+
* Get information and statistics about the Redis server. DEFAULT option is assumed
92+
*
93+
* @see <a href="https://redis.io/commands/info/">redis.io</a> for details.
94+
* @return CompletableFuture with the response
95+
*/
96+
@Override
97+
public CompletableFuture<Map> info() {
98+
return commandManager.submitNewCommand(Command.info(), BaseClient::handleMapResponse);
99+
}
100+
101+
/**
102+
* Get information and statistics about the Redis server.
103+
*
104+
* @see <a href="https://redis.io/commands/info/">redis.io</a> for details.
105+
* @param options - A list of InfoSection values specifying which sections of information to
106+
* retrieve. When no parameter is provided, the default option is assumed.
107+
* @return CompletableFuture with the response
108+
*/
109+
@Override
110+
public CompletableFuture<Map> info(InfoOptions options) {
111+
return commandManager.submitNewCommand(
112+
Command.info(options.toInfoOptions()), BaseClient::handleMapResponse);
113+
}
114+
115+
/**
116+
* Get the value associated with the given key, or null if no such value exists.
117+
*
118+
* @see <a href="https://redis.io/commands/get/">redis.io</a> for details.
119+
* @param key - The key to retrieve from the database.
120+
* @return If `key` exists, returns the value of `key` as a string. Otherwise, return null
121+
*/
122+
@Override
123+
public CompletableFuture<String> get(String key) {
124+
return commandManager.submitNewCommand(Command.get(key), BaseClient::handleStringResponse);
125+
}
126+
127+
/**
128+
* Set the given key with the given value.
129+
*
130+
* @see <a href="https://redis.io/commands/set/">redis.io</a> for details.
131+
* @param key - The key to store.
132+
* @param value - The value to store with the given key.
133+
* @return null
134+
*/
135+
@Override
136+
public CompletableFuture<Void> set(String key, String value) {
137+
return commandManager.submitNewCommand(Command.set(key, value), BaseClient::handleVoidResponse);
138+
}
139+
140+
/**
141+
* Set the given key with the given value. Return value is dependent on the passed options.
142+
*
143+
* @see <a href="https://redis.io/commands/set/">redis.io</a> for details.
144+
* @param key - The key to store.
145+
* @param value - The value to store with the given key.
146+
* @param options - The Set options
147+
* @return string or null If value isn't set because of `onlyIfExists` or `onlyIfDoesNotExist`
148+
* conditions, return null. If `returnOldValue` is set, return the old value as a string.
149+
*/
60150
@Override
61-
public CompletableFuture<Object> customCommand(String[] args) {
62-
Command command =
63-
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
64-
return commandManager.submitNewCommand(command, this::handleObjectResponse);
151+
public CompletableFuture<String> set(String key, String value, SetOptions options) {
152+
return commandManager.submitNewCommand(
153+
Command.set(key, value, options.toArgs()), BaseClient::handleStringResponse);
65154
}
66155
}

java/client/src/main/java/glide/api/commands/BaseCommands.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22

33
import java.util.concurrent.CompletableFuture;
44

5-
/**
6-
* Base Commands interface to handle generic command and transaction requests.
7-
*
8-
* @param <T> The data return type.
9-
*/
10-
public interface BaseCommands<T> {
5+
/** Base Commands interface to handle generic command and transaction requests. */
6+
public interface BaseCommands {
117

128
/**
139
* Executes a single command, without checking inputs. Every part of the command, including
@@ -25,5 +21,20 @@ public interface BaseCommands<T> {
2521
* @param args Arguments for the custom command including the command name
2622
* @return A <em>CompletableFuture</em> with response result from Redis
2723
*/
28-
CompletableFuture<T> customCommand(String[] args);
24+
CompletableFuture<Object> customCommand(String[] args);
25+
26+
/**
27+
* Execute a transaction by processing the queued commands.
28+
*
29+
* @see <a href="https://redis.io/topics/Transactions/">redis.io</a> for details on Redis
30+
* Transactions.
31+
* @param transaction - A {@link Transaction} object containing a list of commands to be executed.
32+
* @return A list of results corresponding to the execution of each command in the transaction.
33+
* <ul>
34+
* <li>If a command returns a value, it will be included in the list. If a command doesn't
35+
* return a value, the list entry will be null.
36+
* <li>If the transaction failed due to a WATCH command, `exec` will return `null`.
37+
* </ul>
38+
*/
39+
CompletableFuture<Object[]> exec(Transaction transaction);
2940
}

java/client/src/main/java/glide/api/commands/ClusterBaseCommands.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package glide.api.commands;
22

3+
import glide.api.models.ClusterValue;
34
import glide.api.models.configuration.Route;
45
import java.util.concurrent.CompletableFuture;
56

67
/**
78
* Base Commands interface to handle generic command and transaction requests with routing options.
8-
*
9-
* @param <T> The data return type.
109
*/
11-
public interface ClusterBaseCommands<T> extends BaseCommands<T> {
10+
public interface ClusterBaseCommands {
1211

1312
/**
1413
* Executes a single command, without checking inputs. Every part of the command, including
@@ -27,5 +26,5 @@ public interface ClusterBaseCommands<T> extends BaseCommands<T> {
2726
* @param route Routing configuration for the command
2827
* @return A <em>CompletableFuture</em> with response result from Redis
2928
*/
30-
CompletableFuture<T> customCommand(String[] args, Route route);
29+
CompletableFuture<ClusterValue<Object>> customCommand(String[] args, Route route);
3130
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package glide.api.commands;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
/**
6+
* Connection Management Commands interface.
7+
*
8+
* @see: <a href="https://redis.io/commands/?group=connection">Server Management Commands</a>
9+
*/
10+
public interface ConnectionCommands {
11+
12+
CompletableFuture<String> ping();
13+
14+
CompletableFuture<String> ping(String msg);
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package glide.api.commands;
2+
3+
/**
4+
* Generic Commands interface to handle 'general' commands.
5+
*
6+
* @see: <a href="https://redis.io/commands/?group=generic">General Commands</a>
7+
*/
8+
public interface GenericCommands {}

0 commit comments

Comments
 (0)