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

Java: Add DUMP and RESTORE commands #371

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
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 @@ -218,6 +218,8 @@ enum RequestType {
MSetNX = 179;
LPos = 180;
LCS = 181;
Dump = 182;
Restore = 183;
}

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 @@ -188,6 +188,8 @@ pub enum RequestType {
MSetNX = 179,
LPos = 180,
LCS = 181,
Dump = 182,
Restore = 183,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -379,6 +381,8 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::MSetNX => RequestType::MSetNX,
ProtobufRequestType::LPos => RequestType::LPos,
ProtobufRequestType::LCS => RequestType::LCS,
ProtobufRequestType::Dump => RequestType::Dump,
ProtobufRequestType::Restore => RequestType::Restore,
}
}
}
Expand Down Expand Up @@ -566,6 +570,8 @@ impl RequestType {
RequestType::MSetNX => Some(cmd("MSETNX")),
RequestType::LPos => Some(cmd("LPOS")),
RequestType::LCS => Some(cmd("LCS")),
RequestType::Dump => Some(cmd("DUMP")),
RequestType::Restore => Some(cmd("RESTORE")),
}
}
}
25 changes: 25 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 @@ -94,6 +95,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 @@ -172,6 +174,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 @@ -1863,4 +1866,26 @@ public CompletableFuture<Long> lcsLen(@NonNull String key1, @NonNull String key2
String[] arguments = new String[] {key1, key2, LEN_REDIS_API};
return commandManager.submitNewCommand(LCS, arguments, this::handleLongResponse);
}

@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,63 @@ 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[] value1 = client.dump("myKey").get();
*
* byte[] value2 = client.dump("nonExistingKey").get();
* assert value2.equals(null);
* }</pre>
*/
CompletableFuture<byte[]> dump(byte[] key);

/**
* Create a <code>key</code> associated with a <code>value</code> that is obtained by
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* 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 `0`, it means creation without any expiry.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* @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 value1 = client.restore("newKey", 0, "value").get();
* assert value1.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 DUMP).
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 0, it means creation without any expiry.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* @param value The serialized value.
* @param restoreOptions The restore option that contains keys and arguments for the restore.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* @return Return <code>OK</code> if successfully create a <code>key</code> with a <code>value
* </code>. Return a "Target key name is busy" error when <code>key</code> already exists
* unless use the <code>REPLACE</code> modifier. If RDB version and data checksum don't match,
* an error is returned.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* @example
* <pre>{@code
* String value1 = client.restore("newKey", 0, "value", RestoreOptions.builder().hasReplace(true).hasAbsttl(true)
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* .seconds(10).frequency(5).build()).get();
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* assert value1.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,86 @@
/** 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 java.util.Optional;
import lombok.*;

/**
* Optional arguments to {@link GenericBaseCommands#restore(byte[], long, byte[], RestoreOptions)}
*
* @see <a href="https://valkey.io/commands/restore/">valkey.io</a>
*/
@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 */
private final boolean hasReplace;
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved

/** When `true`, it represents <code>ABSTTL</code> keyword has been used */
private final boolean hasAbsttl;

/** It represents the idletime of object */
private final Optional<Long> seconds;
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved

/** It represents the frequency of object */
private final Optional<Long> frequency;

/**
* 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);

yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
if (hasReplace) {
resultList.add(REPLACE_REDIS_API.getBytes());
}

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

if (seconds != null) {
seconds.ifPresent(
sec -> {
if (sec > 0) {
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
resultList.add(IDLETIME_REDIS_API.getBytes());
resultList.add(Long.toString(sec).getBytes());
}
});
}

if (frequency != null) {
frequency.ifPresent(
freq -> {
if (freq > 0) {
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
resultList.add(FREQ_REDIS_API.getBytes());
resultList.add(Long.toString(freq).getBytes());
}
});
}

return resultList;
}
}
41 changes: 41 additions & 0 deletions java/client/src/test/java/glide/api/ByteArrayArgumentMatcher.java
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

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) {
int length = t.size();

for (int index = 0; index < length; index++) {
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
if (!(new String(t.get(index)).equals(new String(arguments.get(index))))) return false;
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
}
}
Loading
Loading