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

[Merge to main] Support transactions for JSON commands #2862

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#### Changes
* Java: Add transaction commands for JSON module ([#2691](https://github.com/valkey-io/valkey-glide/pull/2691))
* Node transaction commands json([#2690](https://github.com/valkey-io/valkey-glide/pull/2690))
* Python: Add JSON commands in transaction ([#2684](https://github.com/valkey-io/valkey-glide/pull/2684))
* Node, Python: Add allow uncovered slots scanning flag option in cluster scan ([#2814](https://github.com/valkey-io/valkey-glide/pull/2814), [#2815](https://github.com/valkey-io/valkey-glide/pull/2815))
* Go: Add HINCRBY command ([#2847](https://github.com/valkey-io/valkey-glide/pull/2847))
* Go: Add HINCRBYFLOAT command ([#2846](https://github.com/valkey-io/valkey-glide/pull/2846))
Expand Down Expand Up @@ -111,6 +114,7 @@
* Node: Add `JSON.NUMINCRBY` and `JSON.NUMMULTBY` command ([#2555](https://github.com/valkey-io/valkey-glide/pull/2555))
* Core: Add support to Availability Zone Affinity read strategy ([#2539](https://github.com/valkey-io/valkey-glide/pull/2539))
* Core: Fix list of readonly commands ([#2634](https://github.com/valkey-io/valkey-glide/pull/2634), [#2649](https://github.com/valkey-io/valkey-glide/pull/2649))
* Java: Add transaction commands for JSON module ([#2691](https://github.com/valkey-io/valkey-glide/pull/2691))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplication with line 2

* Core: Improve retry logic and update unmaintained dependencies for Rust lint CI ([#2673](https://github.com/valkey-io/valkey-glide/pull/2643))
* Core: Release the read lock while creating connections in `refresh_connections` ([#2630](https://github.com/valkey-io/valkey-glide/issues/2630))
* Core: SlotMap refactor - Added NodesMap, Update the slot map upon MOVED errors ([#2682](https://github.com/valkey-io/valkey-glide/issues/2682))
Expand Down
3 changes: 2 additions & 1 deletion glide-core/redis-rs/redis/src/cluster_routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,8 @@ fn base_routing(cmd: &[u8]) -> RouteBy {
| b"OBJECT ENCODING"
| b"OBJECT FREQ"
| b"OBJECT IDLETIME"
| b"OBJECT REFCOUNT" => RouteBy::SecondArg,
| b"OBJECT REFCOUNT"
| b"JSON.DEBUG" => RouteBy::SecondArg,

b"LMPOP" | b"SINTERCARD" | b"ZDIFF" | b"ZINTER" | b"ZINTERCARD" | b"ZMPOP" | b"ZUNION" => {
RouteBy::SecondArgAfterKeyCount
Expand Down
1,205 changes: 1,205 additions & 0 deletions java/client/src/main/java/glide/api/commands/servermodules/MultiJson.java

Large diffs are not rendered by default.

31 changes: 2 additions & 29 deletions java/client/src/main/java/glide/api/models/BaseTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@
import static glide.api.models.commands.stream.StreamReadOptions.READ_COUNT_VALKEY_API;
import static glide.api.models.commands.stream.XInfoStreamOptions.COUNT;
import static glide.api.models.commands.stream.XInfoStreamOptions.FULL;
import static glide.utils.ArgsBuilder.checkTypeOrThrow;
import static glide.utils.ArgsBuilder.newArgsBuilder;
import static glide.utils.ArrayTransformUtils.flattenAllKeysFollowedByAllValues;
import static glide.utils.ArrayTransformUtils.flattenMapToGlideStringArray;
import static glide.utils.ArrayTransformUtils.flattenMapToGlideStringArrayValueFirst;
Expand Down Expand Up @@ -7267,35 +7269,6 @@ protected ArgsArray emptyArgs() {
return commandArgs.build();
}

protected ArgsBuilder newArgsBuilder() {
return new ArgsBuilder();
}

protected <ArgType> void checkTypeOrThrow(ArgType arg) {
if ((arg instanceof String) || (arg instanceof GlideString)) {
return;
}
throw new IllegalArgumentException("Expected String or GlideString");
}

protected <ArgType> void checkTypeOrThrow(ArgType[] args) {
if (args.length == 0) {
// nothing to check here
return;
}
checkTypeOrThrow(args[0]);
}

protected <ArgType> void checkTypeOrThrow(Map<ArgType, ArgType> argsMap) {
if (argsMap.isEmpty()) {
// nothing to check here
return;
}

var arg = argsMap.keySet().iterator().next();
checkTypeOrThrow(arg);
}

/** Helper function for creating generic type ("ArgType") array */
@SafeVarargs
protected final <ArgType> ArgType[] createArray(ArgType... args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import static command_request.CommandRequestOuterClass.RequestType.PubSubShardChannels;
import static command_request.CommandRequestOuterClass.RequestType.PubSubShardNumSub;
import static command_request.CommandRequestOuterClass.RequestType.SPublish;
import static glide.utils.ArgsBuilder.checkTypeOrThrow;
import static glide.utils.ArgsBuilder.newArgsBuilder;

import glide.api.GlideClusterClient;
import lombok.NonNull;
Expand Down
2 changes: 2 additions & 0 deletions java/client/src/main/java/glide/api/models/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import static command_request.CommandRequestOuterClass.RequestType.Select;
import static glide.api.commands.GenericBaseCommands.REPLACE_VALKEY_API;
import static glide.api.commands.GenericCommands.DB_VALKEY_API;
import static glide.utils.ArgsBuilder.checkTypeOrThrow;
import static glide.utils.ArgsBuilder.newArgsBuilder;

import glide.api.GlideClient;
import glide.api.models.commands.scan.ScanOptions;
Expand Down
30 changes: 30 additions & 0 deletions java/client/src/main/java/glide/utils/ArgsBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import glide.api.models.GlideString;
import java.util.ArrayList;
import java.util.Map;

/**
* Helper class for collecting arbitrary type of arguments and stores them as an array of
Expand Down Expand Up @@ -63,4 +64,33 @@ public ArgsBuilder add(int[] args) {
public GlideString[] toArray() {
return argumentsList.toArray(new GlideString[0]);
}

public static <ArgType> void checkTypeOrThrow(ArgType arg) {
if ((arg instanceof String) || (arg instanceof GlideString)) {
return;
}
throw new IllegalArgumentException("Expected String or GlideString");
}

public static <ArgType> void checkTypeOrThrow(ArgType[] args) {
if (args.length == 0) {
// nothing to check here
return;
}
checkTypeOrThrow(args[0]);
}

public static <ArgType> void checkTypeOrThrow(Map<ArgType, ArgType> argsMap) {
if (argsMap.isEmpty()) {
// nothing to check here
return;
}

var arg = argsMap.keySet().iterator().next();
checkTypeOrThrow(arg);
}

public static ArgsBuilder newArgsBuilder() {
return new ArgsBuilder();
}
}
201 changes: 201 additions & 0 deletions java/integTest/src/test/java/glide/modules/JsonTests.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.modules;

import static glide.TestUtilities.assertDeepEquals;
import static glide.TestUtilities.commonClusterClientConfig;
import static glide.api.BaseClient.OK;
import static glide.api.models.GlideString.gs;
Expand All @@ -16,12 +17,15 @@
import com.google.gson.JsonParser;
import glide.api.GlideClusterClient;
import glide.api.commands.servermodules.Json;
import glide.api.commands.servermodules.MultiJson;
import glide.api.models.ClusterTransaction;
import glide.api.models.GlideString;
import glide.api.models.commands.ConditionalChange;
import glide.api.models.commands.FlushMode;
import glide.api.models.commands.InfoOptions.Section;
import glide.api.models.commands.json.JsonArrindexOptions;
import glide.api.models.commands.json.JsonGetOptions;
import java.util.ArrayList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -1225,4 +1229,201 @@ public void json_type() {
// Check for all types in the JSON document using legacy path
assertEquals("string", Json.type(client, key, "[*]").get());
}

@SneakyThrows
@Test
public void transaction_tests() {

ClusterTransaction transaction = new ClusterTransaction();
ArrayList<Object> expectedResult = new ArrayList<>();

String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String key3 = "{key}-3" + UUID.randomUUID();
String key4 = "{key}-4" + UUID.randomUUID();
String key5 = "{key}-5" + UUID.randomUUID();
String key6 = "{key}-6" + UUID.randomUUID();

MultiJson.set(transaction, key1, "$", "{\"a\": \"one\", \"b\": [\"one\", \"two\"]}");
expectedResult.add(OK);

MultiJson.set(
transaction,
key1,
"$",
"{\"a\": \"one\", \"b\": [\"one\", \"two\"]}",
ConditionalChange.ONLY_IF_DOES_NOT_EXIST);
expectedResult.add(null);

MultiJson.get(transaction, key1);
expectedResult.add("{\"a\":\"one\",\"b\":[\"one\",\"two\"]}");

MultiJson.get(transaction, key1, new String[] {"$.a", "$.b"});
expectedResult.add("{\"$.a\":[\"one\"],\"$.b\":[[\"one\",\"two\"]]}");

MultiJson.get(transaction, key1, JsonGetOptions.builder().space(" ").build());
expectedResult.add("{\"a\": \"one\",\"b\": [\"one\",\"two\"]}");

MultiJson.get(
transaction,
key1,
new String[] {"$.a", "$.b"},
JsonGetOptions.builder().space(" ").build());
expectedResult.add("{\"$.a\": [\"one\"],\"$.b\": [[\"one\",\"two\"]]}");

MultiJson.arrappend(
transaction, key1, "$.b", new String[] {"\"3\"", "\"4\"", "\"5\"", "\"6\""});
expectedResult.add(new Object[] {6L});

MultiJson.arrindex(transaction, key1, "$..b", "\"one\"");
expectedResult.add(new Object[] {0L});

MultiJson.arrindex(transaction, key1, "$..b", "\"one\"", new JsonArrindexOptions(0L));
expectedResult.add(new Object[] {0L});

MultiJson.arrinsert(transaction, key1, "$..b", 4, new String[] {"\"7\""});
expectedResult.add(new Object[] {7L});

MultiJson.arrlen(transaction, key1, "$..b");
expectedResult.add(new Object[] {7L});

MultiJson.arrpop(transaction, key1, "$..b", 6L);
expectedResult.add(new Object[] {"\"6\""});

MultiJson.arrpop(transaction, key1, "$..b");
expectedResult.add(new Object[] {"\"5\""});

MultiJson.arrtrim(transaction, key1, "$..b", 2, 3);
expectedResult.add(new Object[] {2L});

MultiJson.objlen(transaction, key1);
expectedResult.add(2L);

MultiJson.objlen(transaction, key1, "$..b");
expectedResult.add(new Object[] {null});

MultiJson.objkeys(transaction, key1, "..");
expectedResult.add(new Object[] {"a", "b"});

MultiJson.objkeys(transaction, key1);
expectedResult.add(new Object[] {"a", "b"});

MultiJson.del(transaction, key1);
expectedResult.add(1L);

MultiJson.set(
transaction,
key1,
"$",
"{\"c\": [1, 2], \"d\": true, \"e\": [\"hello\", \"clouds\"], \"f\": {\"a\": \"hello\"}}");
expectedResult.add(OK);

MultiJson.del(transaction, key1, "$");
expectedResult.add(1L);

MultiJson.set(
transaction,
key1,
"$",
"{\"c\": [1, 2], \"d\": true, \"e\": [\"hello\", \"clouds\"], \"f\": {\"a\": \"hello\"}}");
expectedResult.add(OK);

MultiJson.numincrby(transaction, key1, "$.c[*]", 10.0);
expectedResult.add("[11,12]");

MultiJson.nummultby(transaction, key1, "$.c[*]", 10.0);
expectedResult.add("[110,120]");

MultiJson.strappend(transaction, key1, "\"bar\"", "$..a");
expectedResult.add(new Object[] {8L});

MultiJson.strlen(transaction, key1, "$..a");
expectedResult.add(new Object[] {8L});

MultiJson.type(transaction, key1, "$..a");
expectedResult.add(new Object[] {"string"});

MultiJson.toggle(transaction, key1, "..d");
expectedResult.add(false);

MultiJson.resp(transaction, key1, "$..a");
expectedResult.add(new Object[] {"hellobar"});

MultiJson.del(transaction, key1, "$..a");
expectedResult.add(1L);

// then delete the entire key
MultiJson.del(transaction, key1, "$");
expectedResult.add(1L);

// 2nd key
MultiJson.set(transaction, key2, "$", "[1, 2, true, null, \"tree\", \"tree2\" ]");
expectedResult.add(OK);

MultiJson.arrlen(transaction, key2);
expectedResult.add(6L);

MultiJson.arrpop(transaction, key2);
expectedResult.add("\"tree2\"");

MultiJson.debugFields(transaction, key2);
expectedResult.add(5L);

MultiJson.debugFields(transaction, key2, "$");
expectedResult.add(new Object[] {5L});

// 3rd key
MultiJson.set(transaction, key3, "$", "\"abc\"");
expectedResult.add(OK);

MultiJson.strappend(transaction, key3, "\"bar\"");
expectedResult.add(6L);

MultiJson.strlen(transaction, key3);
expectedResult.add(6L);

MultiJson.type(transaction, key3);
expectedResult.add("string");

MultiJson.resp(transaction, key3);
expectedResult.add("abcbar");

// 4th key
MultiJson.set(transaction, key4, "$", "true");
expectedResult.add(OK);

MultiJson.toggle(transaction, key4);
expectedResult.add(false);

MultiJson.debugMemory(transaction, key4);
expectedResult.add(24L);

MultiJson.debugMemory(transaction, key4, "$");
expectedResult.add(new Object[] {16L});

MultiJson.clear(transaction, key2, "$.a");
expectedResult.add(0L);

MultiJson.clear(transaction, key2);
expectedResult.add(1L);

MultiJson.forget(transaction, key3);
expectedResult.add(1L);

MultiJson.forget(transaction, key4, "$");
expectedResult.add(1L);

// mget, key5 and key6
MultiJson.set(transaction, key5, "$", "{\"a\": 1, \"b\": [\"one\", \"two\"]}");
expectedResult.add(OK);

MultiJson.set(transaction, key6, "$", "{\"a\": 1, \"c\": false}");
expectedResult.add(OK);

MultiJson.mget(transaction, new String[] {key5, key6}, "$.c");
expectedResult.add(new String[] {"[]", "[false]"});

Object[] results = client.exec(transaction).get();
assertDeepEquals(expectedResult.toArray(), results);
}
}
Loading
Loading