Skip to content

Commit

Permalink
Node: add ARRTRIM command (#2550)
Browse files Browse the repository at this point in the history
* Node: add ARRTRIM command

Signed-off-by: Yi-Pin Chen <yi-pin.chen@improving.com>
  • Loading branch information
yipin-chen authored Oct 31, 2024
1 parent 8a20b13 commit 23689db
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
* Java: Added `JSON.TYPE` ([#2525](https://github.com/valkey-io/valkey-glide/pull/2525))
* Node: Added `FT.DROPINDEX` ([#2516](https://github.com/valkey-io/valkey-glide/pull/2516))
* Node: Added `JSON.RESP` ([#2517](https://github.com/valkey-io/valkey-glide/pull/2517))
* Node: Added `JSON.ARRTRIM` ([#2550](https://github.com/valkey-io/valkey-glide/pull/2550))
* Python: Add `JSON.STRAPPEND` , `JSON.STRLEN` commands ([#2372](https://github.com/valkey-io/valkey-glide/pull/2372))
* Python: Add `JSON.OBJKEYS` command ([#2395](https://github.com/valkey-io/valkey-glide/pull/2395))
* Python: Add `JSON.ARRINSERT` command ([#2464](https://github.com/valkey-io/valkey-glide/pull/2464))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ public static CompletableFuture<Object> arrpop(
}

/**
* Trims an array at the specified <code>path</code> within the JSON document started at <code>key
* Trims an array at the specified <code>path</code> within the JSON document stored at <code>key
* </code> so that it becomes a subarray [<code>start</code>, <code>end</code>], both inclusive.
* <br>
* If <code>start</code> < 0, it is treated as 0.<br>
Expand Down Expand Up @@ -1193,7 +1193,7 @@ public static CompletableFuture<Object> arrtrim(
}

/**
* Trims an array at the specified <code>path</code> within the JSON document started at <code>key
* Trims an array at the specified <code>path</code> within the JSON document stored at <code>key
* </code> so that it becomes a subarray [<code>start</code>, <code>end</code>], both inclusive.
* <br>
* If <code>start</code> < 0, it is treated as 0.<br>
Expand Down
71 changes: 67 additions & 4 deletions node/src/server-modules/GlideJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,69 @@ export class GlideJson {
return _executeCommand(client, args);
}

/**
* Trims an array at the specified `path` within the JSON document stored at `key` so that it becomes a subarray [start, end], both inclusive.
* If `start` < 0, it is treated as 0.
* If `end` >= size (size of the array), it is treated as size-1.
* If `start` >= size or `start` > `end`, the array is emptied and 0 is returned.
*
* @param client - The client to execute the command.
* @param key - The key of the JSON document.
* @param path - The path within the JSON document.
* @param start - The start index, inclusive.
* @param end - The end index, inclusive.
* @returns
* - For JSONPath (`path` starts with `$`):
* - Returns a list of integer replies for every possible path, indicating the new length of the array,
* or `null` for JSON values matching the path that are not an array.
* - If the array is empty, its corresponding return value is 0.
* - If `path` doesn't exist, an empty array will be returned.
* - If an index argument is out of bounds, an error is raised.
* - For legacy path (`path` doesn't start with `$`):
* - Returns an integer representing the new length of the array.
* - If the array is empty, its corresponding return value is 0.
* - If multiple paths match, the length of the first trimmed array match is returned.
* - If `path` doesn't exist, or the value at `path` is not an array, an error is raised.
* - If an index argument is out of bounds, an error is raised.
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '[[], ["a"], ["a", "b"], ["a", "b", "c"]]');
* // Output: 'OK' - Indicates successful setting of the value at path '$' in the key stored at `doc`.
* const result = await GlideJson.arrtrim(client, "doc", "$[*]", 0, 1);
* console.log(result);
* // Output: [0, 1, 2, 2]
* console.log(await GlideJson.get(client, "doc", "$"));
* // Output: '[[],["a"],["a","b"],["a","b"]]' - Returns the value at path '$' in the JSON document stored at `doc`.
* ```
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '{"children": ["John", "Jack", "Tom", "Bob", "Mike"]}');
* // Output: 'OK' - Indicates successful setting of the value at path '$' in the key stored at `doc`.
* result = await GlideJson.arrtrim(client, "doc", ".children", 0, 1);
* console.log(result);
* // Output: 2
* console.log(await GlideJson.get(client, "doc", ".children"));
* // Output: '["John", "Jack"]' - Returns the value at path '$' in the JSON document stored at `doc`.
* ```
*/
static async arrtrim(
client: BaseClient,
key: GlideString,
path: GlideString,
start: number,
end: number,
): Promise<ReturnTypeJson<number>> {
const args: GlideString[] = [
"JSON.ARRTRIM",
key,
path,
start.toString(),
end.toString(),
];
return _executeCommand<ReturnTypeJson<number>>(client, args);
}

/**
* Toggles a Boolean value stored at the specified `path` within the JSON document stored at `key`.
*
Expand Down Expand Up @@ -484,7 +547,7 @@ export class GlideJson {
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", "[1, 2.3, "foo", true, null, {}, []]"));
* console.log(await GlideJson.set(client, "doc", "$", '[1, 2.3, "foo", true, null, {}, []]'));
* // Output: 'OK' - Indicates successful setting of the value at path '$' in the key stored at `doc`.
* const result = await GlideJson.type(client, "doc", "$[*]");
* console.log(result);
Expand Down Expand Up @@ -535,7 +598,7 @@ export class GlideJson {
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", ".", "{a: [1, 2, 3], b: {a: [1, 2], c: {a: 42}}}"));
* console.log(await GlideJson.set(client, "doc", ".", '{a: [1, 2, 3], b: {a: [1, 2], c: {a: 42}}}'));
* // Output: 'OK' - Indicates successful setting of the value at path '.' in the key stored at `doc`.
* const result = await GlideJson.resp(client, "doc", {path: "$..a"});
* console.log(result);
Expand Down Expand Up @@ -582,7 +645,7 @@ export class GlideJson {
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '{a:"foo", nested: {a: "hello"}, nested2: {a: 31}}"));
* console.log(await GlideJson.set(client, "doc", "$", '{a:"foo", nested: {a: "hello"}, nested2: {a: 31}}'));
* // Output: 'OK' - Indicates successful setting of the value at path '$' in the key stored at `doc`.
* console.log(await GlideJson.strlen(client, "doc", {path: "$..a"}));
* // Output: [3, 5, null] - The length of the string values at path '$..a' in the key stored at `doc`.
Expand Down Expand Up @@ -632,7 +695,7 @@ export class GlideJson {
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '{a:"foo", nested: {a: "hello"}, nested2: {a: 31}}"));
* console.log(await GlideJson.set(client, "doc", "$", '{a:"foo", nested: {a: "hello"}, nested2: {a: 31}}'));
* // Output: 'OK' - Indicates successful setting of the value at path '$' in the key stored at `doc`.
* console.log(await GlideJson.strappend(client, "doc", jsonpy.dumps("baz"), {path: "$..a"}))
* // Output: [6, 8, null] - The new length of the string values at path '$..a' in the key stored at `doc` after the append operation.
Expand Down
133 changes: 133 additions & 0 deletions node/tests/ServerModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,139 @@ describe("Server Module Tests", () => {
).toBeNull();
});

it("json.arrtrim tests", async () => {
client = await GlideClusterClient.createClient(
getClientConfigurationOption(
cluster.getAddresses(),
protocol,
),
);

const key = uuidv4();
const jsonValue = {
a: [0, 1, 2, 3, 4, 5, 6, 7, 8],
b: { a: [0, 9, 10, 11, 12, 13], c: { a: 42 } },
};

// setup
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");

// Basic trim
expect(
await GlideJson.arrtrim(client, key, "$..a", 1, 7),
).toEqual([7, 5, null]);

// Test end >= size (should be treated as size-1)
expect(
await GlideJson.arrtrim(client, key, "$.a", 0, 10),
).toEqual([7]);
expect(
await GlideJson.arrtrim(client, key, ".a", 0, 10),
).toEqual(7);

// Test negative start (should be treated as 0)
expect(
await GlideJson.arrtrim(client, key, "$.a", -1, 5),
).toEqual([6]);
expect(
await GlideJson.arrtrim(client, key, ".a", -1, 5),
).toEqual(6);

// Test start >= size (should empty the array)
expect(
await GlideJson.arrtrim(client, key, "$.a", 7, 10),
).toEqual([0]);
const jsonValue2 = ["a", "b", "c"];
expect(
await GlideJson.set(
client,
key,
".a",
JSON.stringify(jsonValue2),
),
).toBe("OK");
expect(
await GlideJson.arrtrim(client, key, ".a", 7, 10),
).toEqual(0);

// Test start > end (should empty the array)
expect(
await GlideJson.arrtrim(client, key, "$..a", 2, 1),
).toEqual([0, 0, null]);
const jsonValue3 = ["a", "b", "c", "d"];
expect(
await GlideJson.set(
client,
key,
"..a",
JSON.stringify(jsonValue3),
),
).toBe("OK");
expect(
await GlideJson.arrtrim(client, key, "..a", 2, 1),
).toEqual(0);

// Multiple path match
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");
expect(
await GlideJson.arrtrim(client, key, "..a", 1, 10),
).toEqual(8);

// Test with non-existent path
await expect(
GlideJson.arrtrim(client, key, "nonexistent", 0, 1),
).rejects.toThrow(RequestError);
expect(
await GlideJson.arrtrim(client, key, "$.nonexistent", 0, 1),
).toEqual([]);

// Test with non-array path
expect(await GlideJson.arrtrim(client, key, "$", 0, 1)).toEqual(
[null],
);
await expect(
GlideJson.arrtrim(client, key, ".", 0, 1),
).rejects.toThrow(RequestError);

// Test with non-existent key
await expect(
GlideJson.arrtrim(client, "non_existing_key", "$", 0, 1),
).rejects.toThrow(RequestError);
await expect(
GlideJson.arrtrim(client, "non_existing_key", ".", 0, 1),
).rejects.toThrow(RequestError);

// Test empty array
expect(
await GlideJson.set(
client,
key,
"$.empty",
JSON.stringify([]),
),
).toBe("OK");
expect(
await GlideJson.arrtrim(client, key, "$.empty", 0, 1),
).toEqual([0]);
expect(
await GlideJson.arrtrim(client, key, ".empty", 0, 1),
).toEqual(0);
});

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"json.strlen tests",
async (protocol) => {
Expand Down

0 comments on commit 23689db

Please sign in to comment.