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: JSON commands in transaction #2691

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
* Node: Add `JSON.DEBUG` command ([#2572](https://github.com/valkey-io/valkey-glide/pull/2572))
* Node: Add `JSON.NUMINCRBY` and `JSON.NUMMULTBY` command ([#2555](https://github.com/valkey-io/valkey-glide/pull/2555))
* 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))
* 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))
* Python: Add JSON commands in transaction ([#2684](https://github.com/valkey-io/valkey-glide/pull/2684))
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);
}
}