Skip to content

Commit

Permalink
Java: Add DUMP and RESTORE commands (#371)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yipin-chen authored and acarbonetto committed Jun 20, 2024
1 parent b483015 commit 400a2ec
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 0 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")),
}
}
}
26 changes: 26 additions & 0 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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 @@ -95,6 +96,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 @@ -182,6 +184,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 @@ -2023,4 +2026,27 @@ private Object convertByteArrayToGlideString(Object o) {
}
return o;
}

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

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

@Override
public CompletableFuture<String> restore(
@NonNull byte[] key,
long ttl,
@NonNull byte[] value,
@NonNull RestoreOptions restoreOptions) {
List<byte[]> arguments = restoreOptions.toArgs(key, ttl, value);
return commandManager.submitNewCommand(Restore, arguments, this::handleStringResponse);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

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 +591,64 @@ CompletableFuture<Boolean> pexpireAt(
* }</pre>
*/
CompletableFuture<Boolean> copy(String source, String destination, boolean replace);

/**
* Serialize the value stored at <code>key</code> in a Redis-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(byte[] 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("newKey", 0, "value").get();
* assert result.equals("OK");
* }</pre>
*/
CompletableFuture<String> restore(byte[] 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("newKey", 0, "value", options);
* assert result.equals("OK");
* }</pre>
*/
CompletableFuture<String> restore(
byte[] key, long ttl, byte[] value, RestoreOptions restoreOptions);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.models.commands;

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

/**
* Optional arguments to {@link GenericBaseCommands#restore(byte[], 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(byte[], long, byte[],
* RestoreOptions)}
*
* @return a byte array that holds the sub commands and their arguments.
*/
public List<byte[]> toArgs(byte[] key, long ttl, byte[] value) {
List<byte[]> resultList = new ArrayList<>();

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

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

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

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

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

return resultList;
}

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

public RestoreOptionsBuilder absttl() {
return hasAbsttl(true);
}
}
}
47 changes: 47 additions & 0 deletions java/client/src/test/java/glide/api/ByteArrayArgumentMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import java.util.Arrays;
import java.util.List;
import org.mockito.ArgumentMatcher;

/**
* Argument matcher for comparing lists of byte arrays.
*
* <p>It is used in Mockito verifications to assert that a method call was made with a specific list
* of byte arrays as arguments.
*/
public class ByteArrayArgumentMatcher implements ArgumentMatcher<List<byte[]>> {
List<byte[]> arguments;

/**
* Constructs a new ByteArrayArgumentMatcher with the provided list of byte arrays.
*
* @param arguments The list of byte arrays to match against.
*/
public ByteArrayArgumentMatcher(List<byte[]> arguments) {
this.arguments = arguments;
}

/**
* Matches the provided list of byte arrays against the stored arguments.
*
* @param t The list of byte arrays to match
* @return boolean - true if the provided list of byte arrays matches the stored arguments, false
* Sotherwise.
*/
@Override
public boolean matches(List<byte[]> t) {
// Check if the sizes of both lists are equal
if (t.size() != arguments.size()) {
return false;
}

for (int index = 0; index < t.size(); index++) {
if (!Arrays.equals(arguments.get(index), t.get(index))) {
return false;
}
}
return true;
}
}
Loading

0 comments on commit 400a2ec

Please sign in to comment.