Skip to content

Commit

Permalink
Java: Script Commands (valkey-io#2261)
Browse files Browse the repository at this point in the history
Signed-off-by: TJ Zhang <tj.zhang@improving.com>
Co-authored-by: TJ Zhang <tj.zhang@improving.com>
  • Loading branch information
tjzhang-BQ and TJ Zhang authored Sep 13, 2024
1 parent f9cfe77 commit 3092db4
Show file tree
Hide file tree
Showing 19 changed files with 1,759 additions and 212 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
* Java, Node, Python: Change BITCOUNT end param to optional (Valkey-8) ([#2248](https://github.com/valkey-io/valkey-glide/pull/2248))
* Java, Node, Python: Add NOSCORES option to ZSCAN & NOVALUES option to HSCAN (Valkey-8) ([#2174](https://github.com/valkey-io/valkey-glide/pull/2174))
* Node: Add SCAN command ([#2257](https://github.com/valkey-io/valkey-glide/pull/2257))
* Java: Add Script commands ([#2261](https://github.com/valkey-io/valkey-glide/pull/2261))

#### Breaking Changes
* Java: Update INFO command ([#2274](https://github.com/valkey-io/valkey-glide/pull/2274))
Expand Down
31 changes: 31 additions & 0 deletions java/client/src/main/java/glide/api/GlideClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import static command_request.CommandRequestOuterClass.RequestType.Ping;
import static command_request.CommandRequestOuterClass.RequestType.RandomKey;
import static command_request.CommandRequestOuterClass.RequestType.Scan;
import static command_request.CommandRequestOuterClass.RequestType.ScriptExists;
import static command_request.CommandRequestOuterClass.RequestType.ScriptFlush;
import static command_request.CommandRequestOuterClass.RequestType.ScriptKill;
import static command_request.CommandRequestOuterClass.RequestType.Select;
import static command_request.CommandRequestOuterClass.RequestType.Time;
import static command_request.CommandRequestOuterClass.RequestType.UnWatch;
Expand Down Expand Up @@ -499,4 +502,32 @@ public CompletableFuture<Object[]> scan(
GlideString[] arguments = new ArgsBuilder().add(cursor).add(options.toArgs()).toArray();
return commandManager.submitNewCommand(Scan, arguments, this::handleArrayResponseBinary);
}

@Override
public CompletableFuture<Boolean[]> scriptExists(@NonNull String[] sha1s) {
return commandManager.submitNewCommand(
ScriptExists, sha1s, response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<Boolean[]> scriptExists(@NonNull GlideString[] sha1s) {
return commandManager.submitNewCommand(
ScriptExists, sha1s, response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<String> scriptFlush() {
return commandManager.submitNewCommand(ScriptFlush, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptFlush(@NonNull FlushMode flushMode) {
return commandManager.submitNewCommand(
ScriptFlush, new String[] {flushMode.toString()}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptKill() {
return commandManager.submitNewCommand(ScriptKill, new String[0], this::handleStringResponse);
}
}
108 changes: 108 additions & 0 deletions java/client/src/main/java/glide/api/GlideClusterClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import static command_request.CommandRequestOuterClass.RequestType.PubSubSNumSub;
import static command_request.CommandRequestOuterClass.RequestType.RandomKey;
import static command_request.CommandRequestOuterClass.RequestType.SPublish;
import static command_request.CommandRequestOuterClass.RequestType.ScriptExists;
import static command_request.CommandRequestOuterClass.RequestType.ScriptFlush;
import static command_request.CommandRequestOuterClass.RequestType.ScriptKill;
import static command_request.CommandRequestOuterClass.RequestType.Time;
import static command_request.CommandRequestOuterClass.RequestType.UnWatch;
import static glide.api.commands.ServerManagementCommands.VERSION_VALKEY_API;
Expand All @@ -52,8 +55,11 @@
import glide.api.models.ClusterTransaction;
import glide.api.models.ClusterValue;
import glide.api.models.GlideString;
import glide.api.models.Script;
import glide.api.models.commands.FlushMode;
import glide.api.models.commands.InfoOptions.Section;
import glide.api.models.commands.ScriptArgOptions;
import glide.api.models.commands.ScriptArgOptionsGlideString;
import glide.api.models.commands.function.FunctionRestorePolicy;
import glide.api.models.commands.scan.ClusterScanCursor;
import glide.api.models.commands.scan.ScanOptions;
Expand All @@ -65,9 +71,11 @@
import glide.utils.ArgsBuilder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.NonNull;
import response.ResponseOuterClass.Response;
Expand Down Expand Up @@ -946,6 +954,106 @@ public CompletableFuture<String> functionKill(@NonNull Route route) {
FunctionKill, new String[0], route, this::handleStringResponse);
}

@Override
public CompletableFuture<Object> invokeScript(@NonNull Script script, @NonNull Route route) {
if (script.getBinaryOutput()) {
return commandManager.submitScript(
script, List.of(), route, this::handleBinaryObjectOrNullResponse);
} else {
return commandManager.submitScript(
script, List.of(), route, this::handleObjectOrNullResponse);
}
}

@Override
public CompletableFuture<Object> invokeScript(
@NonNull Script script, @NonNull ScriptArgOptions options, @NonNull Route route) {
return commandManager.submitScript(
script,
options.getArgs().stream().map(GlideString::gs).collect(Collectors.toList()),
route,
script.getBinaryOutput()
? this::handleBinaryObjectOrNullResponse
: this::handleObjectOrNullResponse);
}

@Override
public CompletableFuture<Object> invokeScript(
@NonNull Script script, @NonNull ScriptArgOptionsGlideString options, @NonNull Route route) {
return commandManager.submitScript(
script,
options.getArgs(),
route,
script.getBinaryOutput()
? this::handleBinaryObjectOrNullResponse
: this::handleObjectOrNullResponse);
}

@Override
public CompletableFuture<Boolean[]> scriptExists(@NonNull String[] sha1s) {
return commandManager.submitNewCommand(
ScriptExists, sha1s, response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<Boolean[]> scriptExists(@NonNull GlideString[] sha1s) {
return commandManager.submitNewCommand(
ScriptExists, sha1s, response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<Boolean[]> scriptExists(@NonNull String[] sha1s, @NonNull Route route) {
return commandManager.submitNewCommand(
ScriptExists,
sha1s,
route,
response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<Boolean[]> scriptExists(
@NonNull GlideString[] sha1s, @NonNull Route route) {
return commandManager.submitNewCommand(
ScriptExists,
sha1s,
route,
response -> castArray(handleArrayResponse(response), Boolean.class));
}

@Override
public CompletableFuture<String> scriptFlush() {
return commandManager.submitNewCommand(ScriptFlush, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptFlush(@NonNull FlushMode flushMode) {
return commandManager.submitNewCommand(
ScriptFlush, new String[] {flushMode.toString()}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptFlush(@NonNull Route route) {
return commandManager.submitNewCommand(
ScriptFlush, new String[0], route, this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptFlush(@NonNull FlushMode flushMode, @NonNull Route route) {
return commandManager.submitNewCommand(
ScriptFlush, new String[] {flushMode.toString()}, route, this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptKill() {
return commandManager.submitNewCommand(ScriptKill, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> scriptKill(@NonNull Route route) {
return commandManager.submitNewCommand(
ScriptKill, new String[0], route, this::handleStringResponse);
}

@Override
public CompletableFuture<ClusterValue<Map<String, Map<String, Object>>>> functionStats() {
return commandManager.submitNewCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
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 glide.api.models.commands.ScriptOptionsGlideString;
import glide.api.models.commands.SortOptions;
import glide.api.models.commands.SortOptionsBinary;
import glide.api.models.configuration.ReadFrom;
Expand Down Expand Up @@ -622,80 +619,6 @@ CompletableFuture<Boolean> pexpireAt(
*/
CompletableFuture<Long> pexpiretime(GlideString key);

// TODO move invokeScript to ScriptingAndFunctionsBaseCommands
// TODO add note to invokeScript about routing on cluster client
/**
* Invokes a Lua script.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading and execution will all be handled internally. If
* the script has not already been loaded, it will be loaded automatically using the <code>
* SCRIPT LOAD</code> command. After that, it will be invoked using the <code>EVALSHA </code>
* command.
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @return a value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script("return 'Hello'", false)) {
* String result = (String) client.invokeScript(luaScript).get();
* assert result.equals("Hello");
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script);

/**
* Invokes a Lua script with its keys and arguments.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading, argument preparation, and execution will all be
* handled internally. If the script has not already been loaded, it will be loaded automatically
* using the <code>SCRIPT LOAD</code> command. After that, it will be invoked using the <code>
* EVALSHA</code> command.
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @param options The script option that contains keys and arguments for the script.
* @return a value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script("return { KEYS[1], ARGV[1] }", false)) {
* ScriptOptions scriptOptions = ScriptOptions.builder().key("foo").arg("bar").build();
* Object[] result = (Object[]) client.invokeScript(luaScript, scriptOptions).get();
* assert result[0].equals("foo");
* assert result[1].equals("bar");
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script, ScriptOptions options);

/**
* Invokes a Lua script with its keys and arguments.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading, argument preparation, and execution will all be
* handled internally. If the script has not already been loaded, it will be loaded automatically
* using the <code>SCRIPT LOAD</code> command. After that, it will be invoked using the <code>
* EVALSHA</code> command.
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @param options The script option that contains keys and arguments for the script.
* @return a value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script(gs("return { KEYS[1], ARGV[1] }", true))) {
* ScriptOptionsGlideString scriptOptions = ScriptOptionsGlideString.builder().key(gs("foo")).arg(gs("bar")).build();
* Object[] result = (Object[]) client.invokeScript(luaScript, scriptOptions).get();
* assert result[0].equals(gs("foo"));
* assert result[1].equals(gs("bar"));
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script, ScriptOptionsGlideString options);

/**
* Returns the remaining time to live of <code>key</code> that has a timeout, in milliseconds.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
package glide.api.commands;

import glide.api.models.GlideString;
import glide.api.models.Script;
import glide.api.models.commands.ScriptOptions;
import glide.api.models.commands.ScriptOptionsGlideString;
import glide.api.models.configuration.ReadFrom;
import java.util.concurrent.CompletableFuture;

Expand Down Expand Up @@ -160,4 +163,88 @@ CompletableFuture<Object> fcallReadOnly(
* }</pre>
*/
CompletableFuture<GlideString> scriptShow(GlideString sha1);

/**
* Invokes a Lua script.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading and execution will all be handled internally. If
* the script has not already been loaded, it will be loaded automatically using the <code>
* SCRIPT LOAD</code> command. After that, it will be invoked using the <code>EVALSHA </code>
* command.
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @return A value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script("return 'Hello'", false)) {
* String result = (String) client.invokeScript(luaScript).get();
* assert result.equals("Hello");
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script);

/**
* Invokes a Lua script with its keys and arguments.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading, argument preparation, and execution will all be
* handled internally. If the script has not already been loaded, it will be loaded automatically
* using the <code>SCRIPT LOAD</code> command. After that, it will be invoked using the <code>
* EVALSHA</code> command.
*
* @apiNote When in cluster mode
* <ul>
* <li>all <code>keys</code> in <code>options</code> must map to the same hash slot.
* <li>if no <code>keys</code> are given, command will be routed to a random primary node.
* </ul>
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @param options The script option that contains keys and arguments for the script.
* @return A value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script("return { KEYS[1], ARGV[1] }", false)) {
* ScriptOptions scriptOptions = ScriptOptions.builder().key("foo").arg("bar").build();
* Object[] result = (Object[]) client.invokeScript(luaScript, scriptOptions).get();
* assert result[0].equals("foo");
* assert result[1].equals("bar");
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script, ScriptOptions options);

/**
* Invokes a Lua script with its keys and arguments.<br>
* This method simplifies the process of invoking scripts on the server by using an object that
* represents a Lua script. The script loading, argument preparation, and execution will all be
* handled internally. If the script has not already been loaded, it will be loaded automatically
* using the <code>SCRIPT LOAD</code> command. After that, it will be invoked using the <code>
* EVALSHA</code> command.
*
* @apiNote When in cluster mode
* <ul>
* <li>all <code>keys</code> in <code>options</code> must map to the same hash slot.
* <li>if no <code>keys</code> are given, command will be routed to a random primary node.
* </ul>
*
* @see <a href="https://valkey.io/commands/script-load/">SCRIPT LOAD</a> and <a
* href="https://valkey.io/commands/evalsha/">EVALSHA</a> for details.
* @param script The Lua script to execute.
* @param options The script option that contains keys and arguments for the script.
* @return A value that depends on the script that was executed.
* @example
* <pre>{@code
* try(Script luaScript = new Script(gs("return { KEYS[1], ARGV[1] }", true))) {
* ScriptOptionsGlideString scriptOptions = ScriptOptionsGlideString.builder().key(gs("foo")).arg(gs("bar")).build();
* Object[] result = (Object[]) client.invokeScript(luaScript, scriptOptions).get();
* assert result[0].equals(gs("foo"));
* assert result[1].equals(gs("bar"));
* }
* }</pre>
*/
CompletableFuture<Object> invokeScript(Script script, ScriptOptionsGlideString options);
}
Loading

0 comments on commit 3092db4

Please sign in to comment.