From 144e03a8c51d44d344c98f182ac5509f65e43869 Mon Sep 17 00:00:00 2001 From: Saubhik Kumar Date: Thu, 19 Sep 2024 23:17:47 +0530 Subject: [PATCH] add error handling for no json --- internal/eval/eval.go | 17 +++-- internal/eval/eval_test.go | 148 +++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 160066029..3bbea4270 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -3574,15 +3574,22 @@ func evalJSONOBJKEYS(args []string, store *dstore.Store) []byte { } jsonData := obj.Value + _,err := sonic.Marshal(jsonData) + if err != nil { + return diceerrors.NewErrWithMessage("Existing key has wrong Dice type") + } keysList := make([]interface{},0,1) // If path is root, return all keys of the entire JSON if path == defaultRootPath { - keys := make([]string,0) - for key := range jsonData.(map[string]interface{}) { - keys = append(keys, key) + if utils.GetJSONFieldType(jsonData) == utils.ObjectType { + keys := make([]string,0) + for key := range jsonData.(map[string]interface{}) { + keys = append(keys, key) + } + keysList = append(keysList, keys) + return clientio.Encode(keysList, false) } - keysList = append(keysList, keys) - return clientio.Encode(keysList, false) + return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) } // Parse the JSONPath expression diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index d13de2d77..3b61a79c2 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -75,6 +75,7 @@ func TestEval(t *testing.T) { testEvalGETEX(t, store) testEvalJSONNUMINCRBY(t, store) testEvalCOMMAND(t, store) + testEvalJSONOBJKEYS(t, store) } func testEvalPING(t *testing.T, store *dstore.Store) { @@ -2586,3 +2587,150 @@ func testEvalCOMMAND(t *testing.T, store *dstore.Store) { runEvalTests(t, tests, evalCommand, store) } + +func testEvalJSONOBJKEYS(t *testing.T, store *dstore.Store) { + tests := map[string]evalTestCase{ + "nil value": { + setup: func() {}, + input: nil, + output: []byte("-ERR wrong number of arguments for 'json.objkeys' command\r\n"), + }, + "empty args": { + setup: func() {}, + input: []string{}, + output: []byte("-ERR wrong number of arguments for 'json.objkeys' command\r\n"), + }, + "key does not exist": { + setup: func() {}, + input: []string{"NONEXISTENT_KEY"}, + output: clientio.RespNIL, + }, + "root not object": { + setup: func() { + key := "EXISTING_KEY" + value := "[1]" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY"}, + output: []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"), + }, + "root object objkeys": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY"}, + output: clientio.Encode([]interface{}{[]interface{}{"name", "age", "city"},}, false), + }, + "wildcard no object objkeys": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"name\":\"John\",\"age\":30,\"pets\":null,\"languages\":[\"python\",\"golang\"],\"flag\":false}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$.*"}, + output: []byte("*5\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n"), + }, + "subpath object objkeys": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"person\":{\"name\":\"John\",\"age\":30},\"languages\":[\"python\",\"golang\"]}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$.person"}, + output: clientio.Encode([]interface{}{[]interface{}{"name", "age"},}, false), + }, + "invalid JSONPath": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"name\":\"John\",\"age\":30}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$invalid_path"}, + output: []byte("-ERR parse error at 2 in $invalid_path\r\n"), + }, + "incomapitable type(int)": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"person\":{\"name\":\"John\",\"age\":30},\"languages\":[\"python\",\"golang\"]}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$.person.age"}, + output: []byte("*1\r\n$-1\r\n"), + }, + "incomapitable type(string)": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"person\":{\"name\":\"John\",\"age\":30},\"languages\":[\"python\",\"golang\"]}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$.person.name"}, + output: []byte("*1\r\n$-1\r\n"), + }, + "incomapitable type(array)": { + setup: func() { + key := "EXISTING_KEY" + value := "{\"person\":{\"name\":\"John\",\"age\":30},\"languages\":[\"python\",\"golang\"]}" + var rootData interface{} + _ = sonic.Unmarshal([]byte(value), &rootData) + obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "$.languages"}, + output: []byte("*1\r\n$-1\r\n"), + }, + } + + runEvalTests(t, tests, evalJSONOBJKEYS, store) +} + +func BenchmarkEvalJSONOBJKEYS(b *testing.B) { + sizes := []int{0, 10, 100, 1000, 10000, 100000, 1000000} // Various sizes of JSON objects + store := dstore.NewStore(nil) + + for _, size := range sizes { + b.Run(fmt.Sprintf("JSONObjectSize_%d", size), func(b *testing.B) { + key := fmt.Sprintf("benchmark_json_objkeys_%d", size) + + // Create a large JSON object with the given size + jsonObj := make(map[string]interface{}) + for i := 0; i < size; i++ { + jsonObj[fmt.Sprintf("key%d", i)] = fmt.Sprintf("value%d", i) + } + + // Set the JSON object in the store + args := []string{key, "$", fmt.Sprintf("%v", jsonObj)} + evalJSONSET(args, store) + + b.ResetTimer() + b.ReportAllocs() + + // Benchmark the evalJSONOBJKEYS function + for i := 0; i < b.N; i++ { + _ = evalJSONOBJKEYS([]string{key, "$"}, store) + } + }) + } +} \ No newline at end of file