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 JSON.ARRAPPEND command #2489

Merged
merged 3 commits into from
Oct 21, 2024
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 @@ -17,6 +17,7 @@
* Java: Added `FT.ALIASADD`, `FT.ALIASDEL`, `FT.ALIASUPDATE` ([#2442](https://github.com/valkey-io/valkey-glide/pull/2442))
* Core: Update routing for commands from server modules ([#2461](https://github.com/valkey-io/valkey-glide/pull/2461))
* Node: Added `JSON.SET` and `JSON.GET` ([#2427](https://github.com/valkey-io/valkey-glide/pull/2427))
* Java: Added `JSON.ARRAPPEND` ([#2489](https://github.com/valkey-io/valkey-glide/pull/2489))

#### Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class Json {
private static final String JSON_PREFIX = "JSON.";
public static final String JSON_SET = JSON_PREFIX + "SET";
public static final String JSON_GET = JSON_PREFIX + "GET";
private static final String JSON_ARRAPPEND = JSON_PREFIX + "ARRAPPEND";
private static final String JSON_ARRINSERT = JSON_PREFIX + "ARRINSERT";
private static final String JSON_ARRLEN = JSON_PREFIX + "ARRLEN";

Expand Down Expand Up @@ -390,6 +391,86 @@ public static CompletableFuture<GlideString> get(
new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(paths).toArray());
}

/**
* Appends one or more <code>values</code> to the JSON array at the specified <code>path</code>
* within the JSON document stored at <code>key</code>.
*
* @param client The client to execute the command.
* @param key The <code>key</code> of the JSON document.
* @param path Represents the <code>path</code> within the JSON document where the <code>values
* </code> will be appended.
* @param values The <code>values</code> to append to the JSON array at the specified <code>path
* </code>.
* @return
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>):<br>
* Returns a list of integers for every possible path, indicating the new length of the
* new array after appending <code>values</code>, or <code>null</code> for JSON values
* matching the path that are not an array. If <code>path</code> does not exist, an
* empty array will be returned.
* <li>For legacy path (<code>path</code> doesn't start with <code>$</code>):<br>
* Returns the length of the new array after appending <code>values</code> to the array
* at <code>path</code>. If multiple paths are matched, returns the last updated array.
* If the JSON value at <code>path</code> is not a array or if <code>path</code> doesn't
* exist, an error is raised. If <code>key</code> doesn't exist, an error is raised.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "{\"a\": 1, \"b\": [\"one\", \"two\"]}").get();
* var res = Json.arrappend(client, "doc", "$.b", new String[] {"\"three\""}).get();
* assert Arrays.equals((Object[]) res, new int[] {3}); // New length of the array after appending
* res = Json.arrappend(client, "doc", ".b", new String[] {"\"four\""}).get();
* assert res.equals(4); // New length of the array after appending
* }</pre>
*/
public static CompletableFuture<Object> arrappend(
@NonNull BaseClient client,
@NonNull String key,
@NonNull String path,
@NonNull String[] values) {
return executeCommand(
client, concatenateArrays(new String[] {JSON_ARRAPPEND, key, path}, values));
}

/**
* Appends one or more <code>values</code> to the JSON array at the specified <code>path</code>
* within the JSON document stored at <code>key</code>.
*
* @param client The client to execute the command.
* @param key The <code>key</code> of the JSON document.
* @param path Represents the <code>path</code> within the JSON document where the <code>values
* </code> will be appended.
* @param values The <code>values</code> to append to the JSON array at the specified <code>path
* </code>.
* @return
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>):<br>
* Returns a list of integers for every possible path, indicating the new length of the
* new array after appending <code>values</code>, or <code>null</code> for JSON values
* matching the path that are not an array. If <code>path</code> does not exist, an
* empty array will be returned.
* <li>For legacy path (<code>path</code> doesn't start with <code>$</code>):<br>
* Returns the length of the new array after appending <code>values</code> to the array
* at <code>path</code>. If multiple paths are matched, returns the last updated array.
* If the JSON value at <code>path</code> is not a array or if <code>path</code> doesn't
* exist, an error is raised. If <code>key</code> doesn't exist, an error is raised.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "{\"a\": 1, \"b\": [\"one\", \"two\"]}").get();
* var res = Json.arrappend(client, gs("doc"), gs("$.b"), new GlideString[] {gs("\"three\"")}).get();
* assert Arrays.equals((Object[]) res, new int[] {3}); // New length of the array after appending
* res = Json.arrappend(client, gs("doc"), gs(".b"), new GlideString[] {gs("\"four\"")}).get();
* assert res.equals(4); // New length of the array after appending
* }</pre>
*/
public static CompletableFuture<Object> arrappend(
@NonNull BaseClient client,
@NonNull GlideString key,
@NonNull GlideString path,
@NonNull GlideString[] values) {
return executeCommand(
client, new ArgsBuilder().add(gs(JSON_ARRAPPEND)).add(key).add(path).add(values).toArray());
}

/**
* Inserts one or more values into the array at the specified <code>path</code> within the JSON
* document stored at <code>key</code>, before the given <code>index</code>.
Expand Down
55 changes: 55 additions & 0 deletions java/integTest/src/test/java/glide/modules/JsonTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.gson.JsonParser;
Expand All @@ -20,6 +21,7 @@
import glide.api.models.commands.InfoOptions.Section;
import glide.api.models.commands.json.JsonGetOptions;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -157,6 +159,59 @@ public void json_set_get_formatting() {
assertEquals(expectedGetResult2, actualGetResult2);
}

@Test
@SneakyThrows
public void arrappend() {
String key = UUID.randomUUID().toString();
String doc = "{\"a\": 1, \"b\": [\"one\", \"two\"]}";

assertEquals(OK, Json.set(client, key, "$", doc).get());

assertArrayEquals(
new Object[] {3L},
(Object[]) Json.arrappend(client, key, "$.b", new String[] {"\"three\""}).get());
assertEquals(
5L, Json.arrappend(client, key, ".b", new String[] {"\"four\"", "\"five\""}).get());

String getResult = Json.get(client, key, new String[] {"$"}).get();
String expectedGetResult =
"[{\"a\": 1, \"b\": [\"one\", \"two\", \"three\", \"four\", \"five\"]}]";
assertEquals(JsonParser.parseString(expectedGetResult), JsonParser.parseString(getResult));

assertArrayEquals(
new Object[] {null},
(Object[]) Json.arrappend(client, key, "$.a", new String[] {"\"value\""}).get());

// JSONPath, path doesn't exist
assertArrayEquals(
new Object[] {},
(Object[])
Json.arrappend(client, gs(key), gs("$.c"), new GlideString[] {gs("\"value\"")}).get());

// Legacy path, path doesn't exist
var exception =
assertThrows(
ExecutionException.class,
() -> Json.arrappend(client, key, ".c", new String[] {"\"value\""}).get());

// Legacy path, the JSON value at path is not a array
exception =
assertThrows(
ExecutionException.class,
() -> Json.arrappend(client, key, ".a", new String[] {"\"value\""}).get());

exception =
assertThrows(
ExecutionException.class,
() ->
Json.arrappend(client, "non_existing_key", "$.b", new String[] {"\"six\""}).get());
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved

exception =
assertThrows(
ExecutionException.class,
() -> Json.arrappend(client, "non_existing_key", ".b", new String[] {"\"six\""}).get());
}

@Test
@SneakyThrows
public void arrinsert() {
Expand Down
Loading