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

Command Migration: JSON. ARRAPPEND, ARRPOP, ARRLEN #1062

Merged
merged 24 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
36b70a9
JSON ARRAPPEN ARRLEN ARRPOP refractor
srivastava-yash Oct 10, 2024
b429543
commands meta update
srivastava-yash Oct 10, 2024
fc3c00a
minor changes
srivastava-yash Oct 11, 2024
e64303e
ARRAPPEND, ARRLEN, ARRPOP unittests migrated
srivastava-yash Oct 11, 2024
a5b533d
minor addition
srivastava-yash Oct 13, 2024
95b8376
Json.arrpop integration test fix
srivastava-yash Oct 13, 2024
8bf907e
substituting nil with clientio.NIL
srivastava-yash Oct 16, 2024
c6ff6f1
unit tests update
srivastava-yash Oct 16, 2024
9a5d306
Merge branch 'master' into cmd-migration/json-arr
srivastava-yash Oct 18, 2024
578d2af
merge issues resolved
srivastava-yash Oct 18, 2024
ccfa8c9
integration tests added to RESP
srivastava-yash Oct 18, 2024
51d8c30
Documentation update
srivastava-yash Oct 18, 2024
50feddd
Merge branch 'master' into cmd-migration/json-arr
srivastava-yash Oct 20, 2024
f4ea388
Merge branch 'master' into cmd-migration/json-arr
srivastava-yash Oct 21, 2024
2e4ad38
merge error fix
srivastava-yash Oct 21, 2024
4aa734d
JSON.ARRPOP integration tests fixed
srivastava-yash Oct 21, 2024
7593147
review comments
srivastava-yash Oct 24, 2024
5ed8711
Merge branch 'master' into cmd-migration/json-arr
srivastava-yash Oct 24, 2024
b384f74
documentation review comments
srivastava-yash Oct 25, 2024
7917c09
minor refractor
srivastava-yash Oct 25, 2024
309fd9e
docs update
srivastava-yash Oct 25, 2024
d92391c
websocket integration tests
srivastava-yash Oct 25, 2024
e433b4a
Merge branch 'master' into cmd-migration/json-arr
srivastava-yash Oct 25, 2024
557d98e
Removing WS tests
lucifercr07 Oct 27, 2024
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
35 changes: 23 additions & 12 deletions docs/src/content/docs/commands/JSON.ARRAPPEND.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The `JSON.ARRAPPEND` command in DiceDB is used to append one or more JSON values

## Syntax

```plaintext
```bash
JSON.ARRAPPEND <key> <path> <json_value> [<json_value> ...]
```

Expand All @@ -27,16 +27,27 @@ When the `JSON.ARRAPPEND` command is executed, the specified JSON values are app

## Error Handling

- `(error) ERR key does not exist`: Raised if the specified key does not exist in the DiceDB database.
- `(error) ERR path does not exist`: Raised if the specified path does not exist within the JSON document.
- `(error) ERR path is not an array`: Raised if the specified path does not point to a JSON array.
- `(error) ERR invalid JSON`: Raised if any of the provided JSON values are not valid JSON.
1. `Wrong type of value or key`:
- Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value`
- Occurs when attempting to use the command on a key that contains a non-string value.

2. `Invalid Key`:
- Error Message: `(error) ERR key does not exist`
- Occurs when attempting to use the command on a key that does not exist.

3. `Invalid Path`:
- Error Message: `(error) ERR path %s does not exist`
- Occurs when attempting to use the command on a path that does not exist in the JSON document.

4. `Non Array Value at Path`:
- Error Message: `(error) ERR path is not an array`
- Occurs when attempting to use the command on a path that contains a non-array value.

## Example Usage

### Example 1: Appending a single value to an array

```plaintext
```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRAPPEND myjson .numbers 4
Expand All @@ -47,7 +58,7 @@ OK

### Example 2: Appending multiple values to an array

```plaintext
```bash
127.0.0.1:6379> JSON.SET myjson . '{"fruits": ["apple", "banana"]}'
OK
127.0.0.1:6379> JSON.ARRAPPEND myjson .fruits "cherry" "date"
Expand All @@ -58,23 +69,23 @@ OK

### Example 3: Error when key does not exist

```plaintext
```bash
127.0.0.1:6379> JSON.ARRAPPEND nonexistingkey .array 1
(error) ERR key does not exist
```

### Example 4: Error when path does not exist

```plaintext
```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRAPPEND myjson .nonexistingpath 4
(error) ERR path does not exist
(error) ERR path .nonexistingpath does not exist
```

### Example 5: Error when path is not an array

```plaintext
```bash
127.0.0.1:6379> JSON.SET myjson . '{"object": {"key": "value"}}'
OK
127.0.0.1:6379> JSON.ARRAPPEND myjson .object 4
Expand All @@ -83,7 +94,7 @@ OK

### Example 6: Error when invalid JSON is provided

```plaintext
```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRAPPEND myjson .numbers invalidjson
Expand Down
91 changes: 91 additions & 0 deletions docs/src/content/docs/commands/JSON.ARRPOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: JSON.ARRPOP
description: Documentation for the DiceDB command JSON.ARRPOP
---

The `JSON.ARRPOP` command in DiceDB is used to pop an element from JSON array located at a specified path within a JSON document. This command is part of the DiceDBJSON module, which provides native JSON capabilities in DiceDB.

## Syntax

```bash
JSON.ARRPOP key [path [index]]
```

## Parameters
| Parameter | Description | Type | Required |
|-----------|-------------------------------------------------------------------------------|---------|----------|
| `key` | The key under which the JSON document is stored. | String | Yes |
| `path` | The JSONPath expression that specifies the location within the JSON document. | String | Yes |
| `index` | The index of the element that needs to be popped from the JSON Array at path. | Integer | No |

## Return Value

- `string, number, object, array, boolean`: The element that is popped from the JSON Array.
- `Array`: The elements that are popped from the respective JSON Arrays.

## Behaviour

When the `JSON.ARRPOP` command is executed, the specified element is popped from the array located at the given index at the given path within the JSON document stored under the specified key. If the path does not exist or does not point to an array, an error will be raised.

## Error Handling

1. `Wrong type of value or key`:
- Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value`
- Occurs when attempting to use the command on a key that contains a non-string value.

2. `Invalid Key`:
- Error Message: `(error) ERR key does not exist`
- Occurs when attempting to use the command on a key that does not exist.

3. `Invalid Path`:
- Error Message: `(error) ERR path %s does not exist`
- Occurs when attempting to use the command on a path that does not exist in the JSON document.

4. `Non Array Value at Path`:
- Error Message: `(error) ERR path is not an array`
- Occurs when attempting to use the command on a path that contains a non-array value.

## Example Usage

### Popping value from an array

```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRPOP myjson .numbers 1
(integer) 2
127.0.0.1:6379> JSON.GET myjson
"{\"numbers\":[1,3]}"
```

### Error when key does not exist

```bash
127.0.0.1:6379> JSON.ARRPOP nonexistingkey .array 1
(error) ERR key does not exist
```

### Error when path does not exist

```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRPOP myjson .nonexistingpath 4
(error) ERR path .nonexistingpath does not exist
```

### Error when path is not an array

```bash
127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}'
OK
127.0.0.1:6379> JSON.ARRPOP myjson .numbers 4
(error) ERR path is not an array
```

## Notes

- Ensure that the DiceDBJSON module is loaded in your DiceDB instance to use the `JSON.ARRPOP` command.
- JSONPath expressions are used to navigate and specify the location within the JSON document. Familiarity with JSONPath syntax is beneficial for effective use of this command.

By following this documentation, users can effectively utilize the `JSON.ARRPOP` command to manipulate JSON arrays within DiceDB.
8 changes: 4 additions & 4 deletions integration_tests/commands/http/json_arrpop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func TestJSONARRPOP(t *testing.T) {
Body: map[string]interface{}{"key": "k", "path": "$..invalid*path", "index": "1"},
},
},
expected: []interface{}{"OK", "ERR invalid JSONPath"},
expected: []interface{}{"OK", "ERR Path '$..invalid*path' does not exist"},
},
{
name: "key doesn't exist error",
Expand All @@ -130,7 +130,7 @@ func TestJSONARRPOP(t *testing.T) {
Body: map[string]interface{}{"key": "doc_new"},
},
},
expected: []interface{}{"ERR could not perform this operation on a key that doesn't exist"},
expected: []interface{}{"ERR no such key"},
},
{
name: "arr pop on wrong key type",
Expand All @@ -144,7 +144,7 @@ func TestJSONARRPOP(t *testing.T) {
Body: map[string]interface{}{"key": "doc_new"},
},
},
expected: []interface{}{"OK", "ERR Existing key has wrong Dice type"},
expected: []interface{}{"OK", "WRONGTYPE Operation against a key holding the wrong kind of value"},
},
{
name: "nil response for arr pop",
Expand All @@ -166,8 +166,8 @@ func TestJSONARRPOP(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
for i, cmd := range tc.commands {
result, _ := exec.FireCommand(cmd)
jsonResult, isString := result.(string)

jsonResult, isString := result.(string)
if isString && testutils.IsJSONResponse(jsonResult) {
testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult)
continue
Expand Down
114 changes: 114 additions & 0 deletions integration_tests/commands/resp/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package resp
import (
"gotest.tools/v3/assert"
"testing"
"github.com/dicedb/dice/testutils"
testifyAssert "github.com/stretchr/testify/assert"
)

func TestJsonStrlen(t *testing.T) {
Expand Down Expand Up @@ -319,3 +321,115 @@ func arraysArePermutations[T comparable](a, b []T) bool {

return true
}

func TestJSONARRPOP(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()
FireCommand(conn, "DEL key")

arrayAtRoot := `[0,1,2,3]`
nestedArray := `{"a":2,"b":[0,1,2,3]}`

testCases := []struct {
name string
commands []string
expected []interface{}
assertType []string
jsonResp []bool
nestedArray bool
path string
}{
{
name: "update array at root path",
commands: []string{"json.set key $ " + arrayAtRoot, "json.arrpop key $ 2", "json.get key"},
expected: []interface{}{"OK", int64(2), "[0,1,3]"},
assertType: []string{"equal", "equal", "deep_equal"},
},
{
name: "update nested array",
commands: []string{"json.set key $ " + nestedArray, "json.arrpop key $.b 2", "json.get key"},
expected: []interface{}{"OK", []interface{}{int64(2)}, `{"a":2,"b":[0,1,3]}`},
assertType: []string{"equal", "deep_equal", "na"},
},
}

for _, tcase := range testCases {
t.Run(tcase.name, func(t *testing.T) {
for i := 0; i < len(tcase.commands); i++ {
cmd := tcase.commands[i]
out := tcase.expected[i]
result := FireCommand(conn, cmd)

jsonResult, isString := result.(string)

if isString && testutils.IsJSONResponse(jsonResult) {
testifyAssert.JSONEq(t, out.(string), jsonResult)
continue
}

if tcase.assertType[i] == "equal" {
assert.Equal(t, out, result)
} else if tcase.assertType[i] == "deep_equal" {
assert.Assert(t, arraysArePermutations(out.([]interface{}), result.([]interface{})))
}
}
})
}
}

func TestJsonARRAPPEND(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()
a := `[1,2]`
b := `{"name":"jerry","partner":{"name":"tom","score":[10]},"partner2":{"score":[10,20]}}`
c := `{"name":["jerry"],"partner":{"name":"tom","score":[10]},"partner2":{"name":12,"score":"rust"}}`

testCases := []struct {
name string
commands []string
expected []interface{}
assertType []string
}{

{
name: "JSON.ARRAPPEND with root path",
commands: []string{"json.set a $ " + a, `json.arrappend a $ 3`},
expected: []interface{}{"OK", []interface{}{int64(3)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "JSON.ARRAPPEND nested",
commands: []string{"JSON.SET doc $ " + b, `JSON.ARRAPPEND doc $..score 10`},
expected: []interface{}{"OK", []interface{}{int64(2), int64(3)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "JSON.ARRAPPEND nested with nil",
commands: []string{"JSON.SET doc $ " + c, `JSON.ARRAPPEND doc $..score 10`},
expected: []interface{}{"OK", []interface{}{int64(2), "(nil)"}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "JSON.ARRAPPEND with different datatypes",
commands: []string{"JSON.SET doc $ " + c, "JSON.ARRAPPEND doc $.name 1"},
expected: []interface{}{"OK", []interface{}{int64(2)}},
assertType: []string{"equal", "deep_equal"},
},
}
for _, tcase := range testCases {
FireCommand(conn, "DEL a")
FireCommand(conn, "DEL doc")
t.Run(tcase.name, func(t *testing.T) {
for i := 0; i < len(tcase.commands); i++ {
cmd := tcase.commands[i]
out := tcase.expected[i]
result := FireCommand(conn, cmd)
if tcase.assertType[i] == "equal" {
assert.Equal(t, out, result)
} else if tcase.assertType[i] == "deep_equal" {
assert.Assert(t, arraysArePermutations(out.([]interface{}), result.([]interface{})))
}
}
})
}
}
17 changes: 10 additions & 7 deletions internal/eval/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ var (
Info: `JSON.ARRAPPEND key [path] value [value ...]
Returns an array of integer replies for each path, the array's new size,
or nil, if the matching JSON value is not an array.`,
Eval: evalJSONARRAPPEND,
Arity: -3,
Arity: -3,
IsMigrated: true,
NewEval: evalJSONARRAPPEND,
}
jsonforgetCmdMeta = DiceCmdMeta{
Name: "JSON.FORGET",
Expand All @@ -215,9 +216,10 @@ var (
Returns an array of integer replies.
Returns error response if the key doesn't exist or key is expired or the matching value is not an array.
Error reply: If the number of arguments is incorrect.`,
Eval: evalJSONARRLEN,
Arity: -2,
KeySpecs: KeySpecs{BeginIndex: 1},
Arity: -2,
KeySpecs: KeySpecs{BeginIndex: 1},
IsMigrated: true,
NewEval: evalJSONARRLEN,
}
jsonnummultbyCmdMeta = DiceCmdMeta{
Name: "JSON.NUMMULTBY",
Expand Down Expand Up @@ -265,8 +267,9 @@ var (
Return nil if array is empty or there is no array at the path.
It supports negative index and is out of bound safe.
`,
Eval: evalJSONARRPOP,
Arity: -2,
Arity: -2,
IsMigrated: true,
NewEval: evalJSONARRPOP,
}
jsoningestCmdMeta = DiceCmdMeta{
Name: "JSON.INGEST",
Expand Down
Loading