Skip to content

Commit

Permalink
Java: Add DUMP and RESTORE commands (#1621)
Browse files Browse the repository at this point in the history
* Java: Add DUMP and RESTORE commands (#371)

* Cherry-pick Java: Add DUMP and RESTORE commands

* Fixed codestyle issues

* Addressed review comments

* Added more test to SharedCommandtests

* Change ByteArrayArgumentMatcher to use Arrays.equals() comparison instead

* Addressed review comments
- added custom setter methods for long values
- changed seconds to idletime
- minor comments updated

* Updated few minor comments

* Removed Optional in RestoreOptions and added setter methods for replace and absttl

* Replaced byte[] with GlideString and fixed codestyle issues

* Updated comment in RestoreOptions.java

* Addressed review comments

* Addressed more review comments

* Added the casting in handleBytesOrNullResponse to return byte[]

* Removed unsed tempKey in SharedCommandTests
Removed .getBytes() in RestoreOptions

---------

Co-authored-by: Yi-Pin Chen <[email protected]>
  • Loading branch information
acarbonetto and yipin-chen authored Jun 21, 2024
1 parent 738d38a commit c30228b
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 3 deletions.
2 changes: 2 additions & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ enum RequestType {
XGroupDelConsumer = 190;
RandomKey = 191;
GetEx = 192;
Dump = 193;
Restore = 194;
}

message Command {
Expand Down
6 changes: 6 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ pub enum RequestType {
XGroupDelConsumer = 190,
RandomKey = 191,
GetEx = 192,
Dump = 193,
Restore = 194,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -405,6 +407,8 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::XGroupDelConsumer => RequestType::XGroupDelConsumer,
ProtobufRequestType::RandomKey => RequestType::RandomKey,
ProtobufRequestType::GetEx => RequestType::GetEx,
ProtobufRequestType::Dump => RequestType::Dump,
ProtobufRequestType::Restore => RequestType::Restore,
}
}
}
Expand Down Expand Up @@ -607,6 +611,8 @@ impl RequestType {
RequestType::XGroupDelConsumer => Some(get_two_word_command("XGROUP", "DELCONSUMER")),
RequestType::RandomKey => Some(cmd("RANDOMKEY")),
RequestType::GetEx => Some(cmd("GETEX")),
RequestType::Dump => Some(cmd("DUMP")),
RequestType::Restore => Some(cmd("RESTORE")),
}
}
}
41 changes: 38 additions & 3 deletions java/client/src/main/java/glide/api/BaseClient.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.api.models.GlideString.gs;
import static glide.api.models.commands.bitmap.BitFieldOptions.BitFieldReadOnlySubCommands;
import static glide.api.models.commands.bitmap.BitFieldOptions.BitFieldSubCommands;
import static glide.api.models.commands.bitmap.BitFieldOptions.createBitFieldArgs;
Expand Down Expand Up @@ -30,6 +31,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Decr;
import static redis_request.RedisRequestOuterClass.RequestType.DecrBy;
import static redis_request.RedisRequestOuterClass.RequestType.Del;
import static redis_request.RedisRequestOuterClass.RequestType.Dump;
import static redis_request.RedisRequestOuterClass.RequestType.Exists;
import static redis_request.RedisRequestOuterClass.RequestType.Expire;
import static redis_request.RedisRequestOuterClass.RequestType.ExpireAt;
Expand Down Expand Up @@ -96,6 +98,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.RPushX;
import static redis_request.RedisRequestOuterClass.RequestType.Rename;
import static redis_request.RedisRequestOuterClass.RequestType.RenameNX;
import static redis_request.RedisRequestOuterClass.RequestType.Restore;
import static redis_request.RedisRequestOuterClass.RequestType.SAdd;
import static redis_request.RedisRequestOuterClass.RequestType.SCard;
import static redis_request.RedisRequestOuterClass.RequestType.SDiff;
Expand Down Expand Up @@ -185,6 +188,7 @@
import glide.api.models.commands.RangeOptions.RangeQuery;
import glide.api.models.commands.RangeOptions.ScoreRange;
import glide.api.models.commands.RangeOptions.ScoredRangeQuery;
import glide.api.models.commands.RestoreOptions;
import glide.api.models.commands.ScoreFilter;
import glide.api.models.commands.ScriptOptions;
import glide.api.models.commands.SetOptions;
Expand Down Expand Up @@ -370,7 +374,15 @@ protected String handleStringOrNullResponse(Response response) throws RedisExcep
String.class, EnumSet.of(ResponseFlags.IS_NULLABLE, ResponseFlags.ENCODING_UTF8), response);
}

protected GlideString handleBytesOrNullResponse(Response response) throws RedisException {
protected byte[] handleBytesOrNullResponse(Response response) throws RedisException {
var result =
handleRedisResponse(GlideString.class, EnumSet.of(ResponseFlags.IS_NULLABLE), response);
if (result == null) return null;

return result.getBytes();
}

protected GlideString handleGlideStringOrNullResponse(Response response) throws RedisException {
return handleRedisResponse(GlideString.class, EnumSet.of(ResponseFlags.IS_NULLABLE), response);
}

Expand Down Expand Up @@ -500,7 +512,7 @@ public CompletableFuture<String> get(@NonNull String key) {
@Override
public CompletableFuture<GlideString> get(@NonNull GlideString key) {
return commandManager.submitNewCommand(
Get, new GlideString[] {key}, this::handleBytesOrNullResponse);
Get, new GlideString[] {key}, this::handleGlideStringOrNullResponse);
}

@Override
Expand All @@ -524,7 +536,7 @@ public CompletableFuture<String> getex(@NonNull String key, @NonNull GetExOption
@Override
public CompletableFuture<GlideString> getdel(@NonNull GlideString key) {
return commandManager.submitNewCommand(
GetDel, new GlideString[] {key}, this::handleBytesOrNullResponse);
GetDel, new GlideString[] {key}, this::handleGlideStringOrNullResponse);
}

@Override
Expand Down Expand Up @@ -2045,4 +2057,27 @@ private Object convertByteArrayToGlideString(Object o) {
}
return o;
}

@Override
public CompletableFuture<byte[]> dump(@NonNull GlideString key) {
GlideString[] arguments = new GlideString[] {key};
return commandManager.submitNewCommand(Dump, arguments, this::handleBytesOrNullResponse);
}

@Override
public CompletableFuture<String> restore(
@NonNull GlideString key, long ttl, @NonNull byte[] value) {
GlideString[] arguments = new GlideString[] {key, gs(Long.toString(ttl).getBytes()), gs(value)};
return commandManager.submitNewCommand(Restore, arguments, this::handleStringResponse);
}

@Override
public CompletableFuture<String> restore(
@NonNull GlideString key,
long ttl,
@NonNull byte[] value,
@NonNull RestoreOptions restoreOptions) {
GlideString[] arguments = restoreOptions.toArgs(key, ttl, value);
return commandManager.submitNewCommand(Restore, arguments, this::handleStringResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

import glide.api.models.GlideString;
import glide.api.models.Script;
import glide.api.models.commands.ExpireOptions;
import glide.api.models.commands.RestoreOptions;
import glide.api.models.commands.ScriptOptions;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -590,4 +592,64 @@ CompletableFuture<Boolean> pexpireAt(
* }</pre>
*/
CompletableFuture<Boolean> copy(String source, String destination, boolean replace);

/**
* Serialize the value stored at <code>key</code> in a Valkey-specific format and return it to the
* user.
*
* @see <a href="https://valkey.io/commands/dump/">valkey.io</a> for details.
* @param key The key of the set.
* @return The serialized value of a set.<br>
* If <code>key</code> does not exist, <code>null</code> will be returned.
* @example
* <pre>{@code
* byte[] result = client.dump("myKey").get();
*
* byte[] response = client.dump("nonExistingKey").get();
* assert response.equals(null);
* }</pre>
*/
CompletableFuture<byte[]> dump(GlideString key);

/**
* Create a <code>key</code> associated with a <code>value</code> that is obtained by
* deserializing the provided serialized <code>value</code> (obtained via {@link #dump}).
*
* @see <a href="https://valkey.io/commands/restore/">valkey.io</a> for details.
* @param key The key of the set.
* @param ttl The expiry time (in milliseconds). If <code>0</code>, the <code>key</code> will
* persist.
* @param value The serialized value.
* @return Return <code>OK</code> if successfully create a <code>key</code> with a <code>value
* </code>.
* @example
* <pre>{@code
* String result = client.restore(gs("newKey"), 0, value).get();
* assert result.equals("OK");
* }</pre>
*/
CompletableFuture<String> restore(GlideString key, long ttl, byte[] value);

/**
* Create a <code>key</code> associated with a <code>value</code> that is obtained by
* deserializing the provided serialized <code>value</code> (obtained via {@link #dump}).
*
* @see <a href="https://valkey.io/commands/restore/">valkey.io</a> for details.
* @param key The key of the set.
* @param ttl The expiry time (in milliseconds). If <code>0</code>, the <code>key</code> will
* persist.
* @param value The serialized value.
* @param restoreOptions The restore options. See {@link RestoreOptions}.
* @return Return <code>OK</code> if successfully create a <code>key</code> with a <code>value
* </code>.
* @example
* <pre>{@code
* RestoreOptions options = RestoreOptions.builder().replace().absttl().idletime(10).frequency(10).build()).get();
* // Set restore options with replace and absolute TTL modifiers, object idletime and frequency to 10.
* String result = client.restore(gs("newKey"), 0, value, options).get();
* assert result.equals("OK");
* }</pre>
*/
CompletableFuture<String> restore(
GlideString key, long ttl, byte[] value, RestoreOptions restoreOptions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands;

import static glide.api.models.GlideString.gs;

import glide.api.commands.GenericBaseCommands;
import glide.api.models.GlideString;
import java.util.ArrayList;
import java.util.List;
import lombok.*;

/**
* Optional arguments to {@link GenericBaseCommands#restore(GlideString, long, byte[],
* RestoreOptions)}
*
* @see <a href="https://valkey.io/commands/restore/">valkey.io</a>
*/
@Getter
@Builder
public final class RestoreOptions {
/** <code>REPLACE</code> subcommand string to replace existing key */
public static final String REPLACE_REDIS_API = "REPLACE";

/**
* <code>ABSTTL</code> subcommand string to represent absolute timestamp (in milliseconds) for TTL
*/
public static final String ABSTTL_REDIS_API = "ABSTTL";

/** <code>IDELTIME</code> subcommand string to set Object Idletime */
public static final String IDLETIME_REDIS_API = "IDLETIME";

/** <code>FREQ</code> subcommand string to set Object Frequency */
public static final String FREQ_REDIS_API = "FREQ";

/** When `true`, it represents <code>REPLACE</code> keyword has been used */
@Builder.Default private boolean hasReplace = false;

/** When `true`, it represents <code>ABSTTL</code> keyword has been used */
@Builder.Default private boolean hasAbsttl = false;

/** It represents the idletime of object */
@Builder.Default private Long idletime = null;

/** It represents the frequency of object */
@Builder.Default private Long frequency = null;

/**
* Creates the argument to be used in {@link GenericBaseCommands#restore(GlideString, long,
* byte[], RestoreOptions)}
*
* @return a <code>GlideString</code> array that holds the subcommands and their arguments.
*/
public GlideString[] toArgs(GlideString key, long ttl, byte[] value) {
List<GlideString> resultList = new ArrayList<>();

resultList.add(key);
resultList.add(gs(Long.toString(ttl)));
resultList.add(gs(value));

if (hasReplace) {
resultList.add(gs(REPLACE_REDIS_API));
}

if (hasAbsttl) {
resultList.add(gs(ABSTTL_REDIS_API));
}

if (idletime != null) {
resultList.add(gs(IDLETIME_REDIS_API));
resultList.add(gs(Long.toString(idletime)));
}

if (frequency != null) {
resultList.add(gs(FREQ_REDIS_API));
resultList.add(gs(Long.toString(frequency)));
}

return resultList.toArray(new GlideString[0]);
}

/** Custom setter methods for replace and absttl */
public static class RestoreOptionsBuilder {
public RestoreOptionsBuilder replace() {
return hasReplace(true);
}

public RestoreOptionsBuilder absttl() {
return hasAbsttl(true);
}
}
}
Loading

0 comments on commit c30228b

Please sign in to comment.