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

Support JSON.ARRINDEX #486 #1348

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
176 changes: 176 additions & 0 deletions integration_tests/commands/http/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func runIntegrationTests(t *testing.T, exec *HTTPCommandExecutor, testCases []In
// fmt.Println("hi expected : ", out)
// fmt.Println("hi actual :", result)
assert.JSONEq(t, out.(string), result.(string))
case "deep_equal":
assert.True(t, testutils.ArraysArePermutations(result.([]interface{}), out.([]interface{})))
}
}
})
Expand Down Expand Up @@ -1772,3 +1774,177 @@ func TestJsonARRTRIM(t *testing.T) {
exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "a"}})
exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "b"}})
}

func TestJSONARRINDEX(t *testing.T) {
exec := NewHTTPCommandExecutor()

exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "key"}})
defer exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "key"}})

normalArray := `[0,1,2,3,4,3]`
nestedArray := `{"arrays":[{"arr":[1,2,3]},{"arr":[2,3,4]},{"arr":[1]}]}`
nestedArray2 := `{"a":[3],"nested":{"a":{"b":2,"c":1}}}`

testCases := []IntegrationTestCase{
{
name: "should return array index when given element is present",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{float64(3)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return -1 when given element is not present",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "value": 10}},
},
expected: []interface{}{"OK", []interface{}{float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return array index with start optional param provided",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "values": []string{"3", "4"}}},
},
expected: []interface{}{"OK", []interface{}{float64(5)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return array index with start and stop optional param provided",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "values": []string{"4", "4", "5"}}},
},
expected: []interface{}{"OK", []interface{}{float64(4)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return -1 with start and stop optional param provided where start > stop",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "values": []string{"3", "2", "1"}}},
},
expected: []interface{}{"OK", []interface{}{float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return -1 with start (out of boud) and stop (out of bound) optional param provided",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(normalArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$", "values": []string{"3", "6", "10"}}},
},
expected: []interface{}{"OK", []interface{}{float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return list of array indexes for nested json",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$.arrays.*.arr", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{float64(2), float64(1), float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return list of array indexes for multiple json path",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$..arr", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{float64(2), float64(1), float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return array of length 1 for nested json path, with index",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$.arrays[1].arr", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{float64(1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return empty array for nonexistent path in nested json",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$..arr1", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return -1 for each nonexisting value in nested json",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$..arr", "value": 5}},
},
expected: []interface{}{"OK", []interface{}{float64(-1), float64(-1), float64(-1)}},
assertType: []string{"equal", "equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return nil for non-array path and -1 for array path if value DNE",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray2)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$..a", "value": 2}},
},
expected: []interface{}{"OK", []interface{}{float64(-1), nil}},
assertType: []string{"equal", "deep_equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
{
name: "should return nil for non-array path if value DNE and valid index for array path if value exists",
commands: []HTTPCommand{
{Command: "JSON.SET", Body: map[string]interface{}{"key": "key", "path": "$", "json": json.RawMessage(nestedArray2)}},
{Command: "JSON.ARRINDEX", Body: map[string]interface{}{"key": "key", "path": "$..a", "value": 3}},
},
expected: []interface{}{"OK", []interface{}{float64(0), nil}},
assertType: []string{"equal", "deep_equal"},
cleanUp: []HTTPCommand{
{Command: "DEL", Body: map[string]interface{}{"key": "key"}},
},
},
}

preTestChecksCommand := HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "key"}}
postTestChecksCommand := HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "key"}}
runIntegrationTests(t, exec, testCases, preTestChecksCommand, postTestChecksCommand)
}
162 changes: 162 additions & 0 deletions integration_tests/commands/resp/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1663,3 +1663,165 @@ func TestJsonARRTRIM(t *testing.T) {
})
}
}

func TestJSONARRINDEX(t *testing.T) {
conn := getLocalConnection()
defer conn.Close()

FireCommand(conn, "DEL key")
defer FireCommand(conn, "DEL key")

normalArray := `[0,1,2,3,4,3]`
nestedArray := `{"arrays":[{"arr":[1,2,3]},{"arr":[2,3,4]},{"arr":[1]}]}`
nestedArray2 := `{"a":[3],"nested":{"a":{"b":2,"c":1}}}`

tests := []struct {
name string
commands []string
expected []interface{}
assertType []string
}{
{
name: "should return error if key is not present",
commands: []string{"json.set key $ " + normalArray, "json.arrindex nonExistentKey $ 3"},
expected: []interface{}{"OK", "ERR could not perform this operation on a key that doesn't exist"},
assertType: []string{"equal", "equal"},
},
{
name: "should return error if json path is invalid",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $invalid_path 3"},
expected: []interface{}{"OK", "ERR Path '$invalid_path' does not exist"},
assertType: []string{"equal", "equal"},
},
{
name: "should return error if provided path does not have any data",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $.some_path 3"},
expected: []interface{}{"OK", []interface{}{}},
assertType: []string{"equal", "equal"},
},
{
name: "should return error if invalid start index provided",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3 abc"},
expected: []interface{}{"OK", "ERR Couldn't parse as integer"},
assertType: []string{"equal", "equal"},
},
{
name: "should return error if invalid stop index provided",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3 4 abc"},
expected: []interface{}{"OK", "ERR Couldn't parse as integer"},
assertType: []string{"equal", "equal"},
},
{
name: "should return array index when given element is present",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3"},
expected: []interface{}{"OK", []interface{}{int64(3)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return -1 when given element is not present",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 10"},
expected: []interface{}{"OK", []interface{}{int64(-1)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return array index with start optional param provided",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3 4"},
expected: []interface{}{"OK", []interface{}{int64(5)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return array index with start and stop optional param provided",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 4 4 5"},
expected: []interface{}{"OK", []interface{}{int64(4)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return -1 with start and stop optional param provided where start > stop",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3 2 1"},
expected: []interface{}{"OK", []interface{}{int64(-1)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return -1 with start (out of boud) and stop (out of bound) optional param provided",
commands: []string{"json.set key $ " + normalArray, "json.arrindex key $ 3 6 10"},
expected: []interface{}{"OK", []interface{}{int64(-1)}},
assertType: []string{"equal", "equal"},
},
{
name: "should return list of array indexes for nested json",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $.arrays.*.arr 3"},
expected: []interface{}{"OK", []interface{}{int64(2), int64(1), int64(-1)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return list of array indexes for multiple json path",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr 3"},
expected: []interface{}{"OK", []interface{}{int64(2), int64(1), int64(-1)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return array of length 1 for nested json path, with index",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $.arrays[1].arr 3"},
expected: []interface{}{"OK", []interface{}{int64(1)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return empty array for nonexistent path in nested json",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr1 3"},
expected: []interface{}{"OK", []interface{}{}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return -1 for each nonexisting value in nested json",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr 5"},
expected: []interface{}{"OK", []interface{}{int64(-1), int64(-1), int64(-1)}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return nil for non-array path and -1 for array path if value DNE",
commands: []string{"json.set key $ " + nestedArray2, "json.arrindex key $..a 2"},
expected: []interface{}{"OK", []interface{}{int64(-1), "(nil)"}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should return nil for non-array path if value DNE and valid index for array path if value exists",
commands: []string{"json.set key $ " + nestedArray2, "json.arrindex key $..a 3"},
expected: []interface{}{"OK", []interface{}{int64(0), "(nil)"}},
assertType: []string{"equal", "deep_equal"},
},
{
name: "should handle stop index - 0 which should be last index inclusive",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr 3 1 0", "json.arrindex key $..arr 3 2 0"},
expected: []interface{}{"OK", []interface{}{int64(2), int64(1), int64(-1)}, []interface{}{int64(2), int64(-1), int64(-1)}},
assertType: []string{"equal", "deep_equal", "deep_equal"},
},
{
name: "should handle stop index - -1 which should be last index exclusive",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr 3 1 -1", "json.arrindex key $..arr 3 2 -1"},
expected: []interface{}{"OK", []interface{}{int64(-1), int64(1), int64(-1)}, []interface{}{int64(-1), int64(-1), int64(-1)}},
assertType: []string{"equal", "deep_equal", "deep_equal"},
},
{
name: "should handle negative start index",
commands: []string{"json.set key $ " + nestedArray, "json.arrindex key $..arr 3 -1"},
expected: []interface{}{"OK", []interface{}{int64(2), int64(-1), int64(-1)}},
assertType: []string{"equal", "deep_equal"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
FireCommand(conn, "DEL key")

for i, cmd := range tc.commands {
result := FireCommand(conn, cmd)
expected := tc.expected[i]
if tc.assertType[i] == "equal" {
assert.Equal(t, result, expected)
} else if tc.assertType[i] == "deep_equal" {
assert.True(t, testutils.ArraysArePermutations(result.([]interface{}), expected.([]interface{})))
}
}
})
}
}
Loading
Loading