Skip to content

Commit

Permalink
[Merge to main] Support transactions for JSON commands (#2862)
Browse files Browse the repository at this point in the history
Java, Node, Python: Add transaction commands for JSON module

---------
Signed-off-by: Yi-Pin Chen <[email protected]>
  • Loading branch information
yipin-chen authored Jan 2, 2025
1 parent c5b7837 commit a36f98e
Show file tree
Hide file tree
Showing 14 changed files with 3,714 additions and 409 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#### Changes

* Java, Node, Python: Add transaction commands for JSON module ([#2862](https://github.com/valkey-io/valkey-glide/pull/2862))
* 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))
* Go: Add SUNIONSTORE command ([#2805](https://github.com/valkey-io/valkey-glide/pull/2805))
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 @@ -672,7 +672,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

0 comments on commit a36f98e

Please sign in to comment.