Skip to content

Commit

Permalink
Java: Add GET & SET commands (#919)
Browse files Browse the repository at this point in the history
* Update commandmanager to remove optional<route> argument

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

* Java: Add GET & SET commands

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

* Move IT tests to SharedCommandTests

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

* Spotless

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

* Add nonnull check to API

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

* Fix from merge

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

* Update handleRedisResponse

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

* Use @timeout annotation

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

* Remove extra @nonnull annotations

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

* Merge main

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

* Add NonNull

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

* Add back IT tests

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

* Remove extra tests

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

* Add DELME tests

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

* Minor: clean tests; throw exception

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

---------

Signed-off-by: Andrew Carbonetto <[email protected]>
  • Loading branch information
acarbonetto authored Feb 9, 2024
1 parent ceaa793 commit 8f35f5a
Show file tree
Hide file tree
Showing 8 changed files with 571 additions and 5 deletions.
39 changes: 36 additions & 3 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
package glide.api;

import static glide.ffi.resolvers.SocketListenerResolver.getSocket;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.SetOptions;
import glide.api.models.configuration.BaseClientConfiguration;
import glide.api.models.exceptions.RedisException;
import glide.connectors.handlers.CallbackDispatcher;
Expand All @@ -21,11 +25,17 @@
import java.util.function.BiFunction;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import response.ResponseOuterClass.ConstantResponse;
import response.ResponseOuterClass.Response;

/** Base Client class for Redis */
@AllArgsConstructor
public abstract class BaseClient implements AutoCloseable, ConnectionManagementCommands {
public abstract class BaseClient
implements AutoCloseable, ConnectionManagementCommands, StringCommands {
/** Redis simple string response with "OK" */
public static final String OK = ConstantResponse.OK.toString();

protected final ConnectionManager connectionManager;
protected final CommandManager commandManager;

Expand Down Expand Up @@ -123,14 +133,18 @@ private <T> T handleRedisResponse(Class<T> classType, boolean isNullable, Respon
+ classType.getSimpleName());
}

protected Object handleObjectOrNullResponse(Response response) {
protected Object handleObjectOrNullResponse(Response response) throws RedisException {
return handleRedisResponse(Object.class, true, response);
}

protected String handleStringResponse(Response response) {
protected String handleStringResponse(Response response) throws RedisException {
return handleRedisResponse(String.class, false, response);
}

protected String handleStringOrNullResponse(Response response) throws RedisException {
return handleRedisResponse(String.class, true, response);
}

@Override
public CompletableFuture<String> ping() {
return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse);
Expand All @@ -140,4 +154,23 @@ public CompletableFuture<String> ping() {
public CompletableFuture<String> ping(@NonNull String str) {
return commandManager.submitNewCommand(Ping, new String[] {str}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> get(@NonNull String key) {
return commandManager.submitNewCommand(
GetString, new String[] {key}, this::handleStringOrNullResponse);
}

@Override
public CompletableFuture<String> set(@NonNull String key, @NonNull String value) {
return commandManager.submitNewCommand(
SetString, new String[] {key, value}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> set(
@NonNull String key, @NonNull String value, @NonNull SetOptions options) {
String[] arguments = ArrayUtils.addAll(new String[] {key, value}, options.toArgs());
return commandManager.submitNewCommand(SetString, arguments, this::handleStringOrNullResponse);
}
}
51 changes: 51 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,51 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

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

/**
* String Commands interface to handle single commands.
*
* @see <a href="https://redis.io/commands/?group=string">String Commands</a>
*/
public interface StringCommands {

/**
* Get the value associated with the given <code>key</code>, or <code>null</code> if no such value
* exists.
*
* @see <a href="https://redis.io/commands/get/">redis.io</a> for details.
* @param key The <code>key</code> to retrieve from the database.
* @return Response from Redis. If <code>key</code> exists, returns the <code>value</code> of
* <code>key</code> as a <code>String</code>. Otherwise, return <code>null</code>.
*/
CompletableFuture<String> get(String key);

/**
* Set the given <code>key</code> with the given value.
*
* @see <a href="https://redis.io/commands/set/">redis.io</a> for details.
* @param key The <code>key</code> to store.
* @param value The value to store with the given <code>key</code>.
* @return Response from Redis containing <code>"OK"</code>.
*/
CompletableFuture<String> set(String key, String value);

/**
* 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.
* @param value The value to store with the given key.
* @param options The Set options.
* @return Response from Redis containing a <code>String</code> or <code>null</code> response. If
* the value is successfully set, return <code>"OK"</code>. If value isn't set because of
* {@link ConditionalSet#ONLY_IF_EXISTS} or {@link ConditionalSet#ONLY_IF_DOES_NOT_EXIST}
* conditions, return <code>null</code>. If {@link SetOptionsBuilder#returnOldValue(boolean)}
* is set, return the old value as a <code>String</code>.
*/
CompletableFuture<String> set(String key, String value, SetOptions options);
}
171 changes: 171 additions & 0 deletions java/client/src/main/java/glide/api/models/commands/SetOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands;

import static glide.api.models.commands.SetOptions.ExpiryType.KEEP_EXISTING;
import static glide.api.models.commands.SetOptions.ExpiryType.MILLISECONDS;
import static glide.api.models.commands.SetOptions.ExpiryType.SECONDS;
import static glide.api.models.commands.SetOptions.ExpiryType.UNIX_MILLISECONDS;
import static glide.api.models.commands.SetOptions.ExpiryType.UNIX_SECONDS;

import glide.api.commands.StringCommands;
import java.util.ArrayList;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import redis_request.RedisRequestOuterClass.Command;

/**
* Optional arguments for {@link StringCommands#set(String, String, SetOptions)} command.
*
* @see <a href="https://redis.io/commands/set/">redis.io</a>
*/
@Builder
public final class SetOptions {

/**
* If <code>conditionalSet</code> is not set the value will be set regardless of prior value
* existence. If value isn't set because of the condition, command will return <code>null</code>.
*/
private final ConditionalSet conditionalSet;

/**
* Set command to return the old string stored at <code>key</code>, or <code>null</code> if <code>
* key</code> did not exist. An error is returned and <code>SET</code> aborted if the value stored
* at <code>key</code> is not a string. Equivalent to <code>GET</code> in the Redis API.
*/
private final boolean returnOldValue;

/** If not set, no expiry time will be set for the value. */
private final Expiry expiry;

/** Conditions which define whether new value should be set or not. */
@RequiredArgsConstructor
@Getter
public enum ConditionalSet {
/**
* Only set the key if it does not already exist. Equivalent to <code>XX</code> in the Redis
* API.
*/
ONLY_IF_EXISTS("XX"),
/** Only set the key if it already exists. Equivalent to <code>NX</code> in the Redis API. */
ONLY_IF_DOES_NOT_EXIST("NX");

private final String redisApi;
}

/** Configuration of value lifetime. */
public static final class Expiry {

/** Expiry type for the time to live */
private final ExpiryType type;

/**
* The amount of time to live before the key expires. Ignored when {@link
* ExpiryType#KEEP_EXISTING} type is set.
*/
private Long count;

private Expiry(ExpiryType type) {
this.type = type;
}

private Expiry(ExpiryType type, Long count) {
this.type = type;
this.count = count;
}

/**
* Retain the time to live associated with the key. Equivalent to <code>KEEPTTL</code> in the
* Redis API.
*/
public static Expiry KeepExisting() {
return new Expiry(KEEP_EXISTING);
}

/**
* Set the specified expire time, in seconds. Equivalent to <code>EX</code> in the Redis API.
*
* @param seconds time to expire, in seconds
* @return Expiry
*/
public static Expiry Seconds(Long seconds) {
return new Expiry(SECONDS, seconds);
}

/**
* Set the specified expire time, in milliseconds. Equivalent to <code>PX</code> in the Redis
* API.
*
* @param milliseconds time to expire, in milliseconds
* @return Expiry
*/
public static Expiry Milliseconds(Long milliseconds) {
return new Expiry(MILLISECONDS, 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
* @return Expiry
*/
public static Expiry UnixSeconds(Long unixSeconds) {
return new Expiry(UNIX_SECONDS, 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
* @return Expiry
*/
public static Expiry UnixMilliseconds(Long unixMilliseconds) {
return new Expiry(UNIX_MILLISECONDS, unixMilliseconds);
}
}

/** Types of value expiration configuration. */
@RequiredArgsConstructor
protected enum ExpiryType {
KEEP_EXISTING("KEEPTTL"),
SECONDS("EX"),
MILLISECONDS("PX"),
UNIX_SECONDS("EXAT"),
UNIX_MILLISECONDS("PXAT");

private final String redisApi;
}

/** String representation of {@link #returnOldValue} when set. */
public static final String RETURN_OLD_VALUE = "GET";

/**
* Converts SetOptions into a String[] to add to a {@link Command} arguments.
*
* @return String[]
*/
public String[] toArgs() {
List<String> optionArgs = new ArrayList<>();
if (conditionalSet != null) {
optionArgs.add(conditionalSet.redisApi);
}

if (returnOldValue) {
optionArgs.add(RETURN_OLD_VALUE);
}

if (expiry != null) {
optionArgs.add(expiry.type.redisApi);
if (expiry.type != KEEP_EXISTING) {
assert expiry.count != null
: "Set command received expiry type " + expiry.type + ", but count was not set.";
optionArgs.add(expiry.count.toString());
}
}

return optionArgs.toArray(new String[0]);
}
}
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.managers;

import static glide.api.BaseClient.OK;

import glide.api.models.exceptions.RedisException;
import lombok.AllArgsConstructor;
import response.ResponseOuterClass.Response;
Expand All @@ -25,8 +27,7 @@ public Object apply(Response response) throws RedisException {
assert !response.hasRequestError() : "Unhandled response request error";

if (response.hasConstantResponse()) {
// Return "OK"
return response.getConstantResponse().toString();
return OK;
}
if (response.hasRespPointer()) {
// Return the shared value - which may be a null value
Expand Down
Loading

0 comments on commit 8f35f5a

Please sign in to comment.