diff --git a/internal/eval/eval.go b/internal/eval/eval.go index a4f2837e8..5edff1a0e 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -455,7 +455,323 @@ func evalJSONMGET(args []string, store *dstore.Store) []byte { return clientio.Encode(interfaceObj, false) } +<<<<<<< HEAD func jsonMGETHelper(store *dstore.Store, path, key string) (result interface{}, err2 []byte) { +======= +// Helper function to check if there are any non-legacy paths +func containsNonLegacyPath(paths []string) bool { + for _, path := range paths { + if isPathLegacy(path) { + return false + } + } + return true +} + +func isPathLegacy(path string) bool { + return strings.HasPrefix(path, "$") +} + +func jsonGETSingle(store *dstore.Store, path, key string, isLegacy bool) (results interface{}, err2 []byte) { + if isLegacy { + return jsonGETSingleLegacy(store, path, key) + } + return jsonGETSingleNormal(store, path, key) +} + +func jsonGETSingleNormal(store *dstore.Store, path, key string) (results interface{}, err2 []byte) { + // Retrieve the object from the store + obj := store.Get(key) + if obj == nil { + return nil, nil // Key does not exist + } + + // Ensure the object type is JSON + err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON) + if err != nil { + return nil, err + } + + jsonData := obj.Value + + // Parse the path using jp.ParseString + expr, parseErr := jp.ParseString(path) + if parseErr != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path \"$.%s\" does not exist", path)) + } + + // Execute the JSONPath query + pathResults := expr.Get(jsonData) + const emptyJSONArray = "[]" + if len(pathResults) == 0 { + return emptyJSONArray, nil + } + + // Serialize the result + resultBytes, marshalErr := sonic.Marshal(pathResults) + if marshalErr != nil { + return nil, diceerrors.NewErrWithMessage("could not serialize result") + } + return string(resultBytes), nil +} + +func jsonGETSingleLegacy(store *dstore.Store, path, key string) (results interface{}, err2 []byte) { + // Retrieve the object from the store + obj := store.Get(key) + if obj == nil { + return nil, nil // Key does not exist + } + + // Ensure the object type is JSON + err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON) + if err != nil { + return nil, err + } + + jsonData := obj.Value + + // Handle legacy paths + if path == "" || path == "." { + // For no path or dot path, return entire data + resultBytes, err := sonic.Marshal(jsonData) + if err != nil { + return nil, diceerrors.NewErrWithMessage("could not serialize result") + } + return string(resultBytes), nil + } + + // Checking for legacy paths that begin with a dot (e.g., ".a") + if path[0] == '.' { + path = path[1:] + } + + expr, parseErr := jp.ParseString(path) + if parseErr != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path \"$.%s\" does not exist", path)) + } + + // Execute the JSONPath query for filtering + pathResults := expr.Get(jsonData) + + if len(pathResults) == 0 { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path \"$.%s\" does not exist", path)) + } + + // Serialize the result + resultBytes, marshalErr := sonic.Marshal(pathResults[0]) + if marshalErr != nil { + return nil, diceerrors.NewErrWithMessage("could not serialize result") + } + return string(resultBytes), nil +} + +func jsonGETMulti(store *dstore.Store, paths []string, key string, isLegacy bool) (multiResults interface{}, err2 []byte) { + // Retrieve the object by key + obj := store.Get(key) + if obj == nil { + return nil, nil // Key does not exist + } + + // Ensure the object is a valid JSON object + err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON) + if err != nil { + return nil, err + } + + jsonData := obj.Value + results := make(map[string]interface{}) + var missingPath string + + // Process each path + for _, path := range paths { + expr, parseErr := jp.ParseString(path) + if parseErr != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path '%s' does not exist", path)) + } + + pathResults := expr.Get(jsonData) + if len(pathResults) == 0 { + // For non-legacy mode, add an empty array if no result found + if !isLegacy { + results[path] = []interface{}{} + } else { + // In legacy mode, immediately mark as missing if no result found + missingPath = path + break + } + } else { + // Add results based on legacy mode + if isLegacy { + results[path] = pathResults[0] // Only the first result + } else { + results[path] = pathResults // All results + } + } + } + + // In legacy mode, throw an error if a missing path was found + if isLegacy && missingPath != "" { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path '%s' does not exist", missingPath)) + } + + // Marshal the results to JSON + resultBytes, marshalErr := sonic.Marshal(results) + if marshalErr != nil { + return nil, diceerrors.NewErrWithMessage("could not serialize result") + } + + return string(resultBytes), nil +} + +// Convert a single value to RESP3 format +func valueToResp3(value interface{}) interface{} { + // Convert the value to the appropriate RESP3 format + switch v := value.(type) { + case string: + return []interface{}{v} + case []interface{}: + return v + default: + return []interface{}{v} + } +} + +// Process a single path and convert its results to RESP3 format +func toResp3Path(path string, jsonData interface{}, isLegacy bool) (resp3Result interface{}, err2 []byte) { + expr, parseErr := jp.ParseString(path) + if parseErr != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path '%s' does not exist", path)) + } + + // Get the results for the current path + pathResults := expr.Get(jsonData) + if len(pathResults) == 0 { + // For non-legacy mode, return an empty array if no result found + if !isLegacy { + return []interface{}{}, nil + } + + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path '%s' does not exist", path)) + } + + // Convert the results to RESP3 format. + resp3Results := []interface{}{} + for _, result := range pathResults { + resp3Results = append(resp3Results, valueToResp3(result)) + } + + return resp3Results, nil +} + +func jsonGETResp3(store *dstore.Store, paths []string, key string, isLegacy bool) (resp3Result interface{}, err2 []byte) { + // Retrieve the object by key + obj := store.Get(key) + if obj == nil { + return nil, nil // Key does not exist + } + + // Ensure the object is a valid JSON object + err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON) + if err != nil { + return nil, err + } + + jsonData := obj.Value + + if len(paths) == 1 { + switch paths[0] { + case "$": + resultBytes, err := sonic.Marshal([]interface{}{jsonData}) + if err != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Could not serialize jsonData for '$': %v", err)) + } + return string(resultBytes), nil + + case ".": + resultBytes, err := sonic.Marshal(jsonData) + if err != nil { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Could not serialize jsonData for '.': %v", err)) + } + return string(resultBytes), nil + } + } + + results := make(map[string]interface{}) + var missingPath string + + // Process each path and convert to RESP3 + for _, path := range paths { + resp3Result, respErr := toResp3Path(path, jsonData, isLegacy) + if respErr != nil { + return nil, respErr + } + + // Add to the result map with correct indexing for legacy mode + if isLegacy { + // Assert that resp3Result is a []interface{} before indexing + if resultSlice, ok := resp3Result.([]interface{}); ok && len(resultSlice) > 0 { + results[path] = resultSlice[0] // Only the first result in legacy mode + } else { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Expected array for path '%s', but got different type", path)) + } + } else { + results[path] = resp3Result // Store all results in non-legacy mode + } + } + + // In legacy mode, throw an error if a missing path was found + if isLegacy && missingPath != "" { + return nil, diceerrors.NewErrWithMessage(fmt.Sprintf("Path '%s' does not exist", missingPath)) + } + + // Marshal the results to JSON (or RESP3) + resultBytes, marshalErr := sonic.Marshal(results) + if marshalErr != nil { + return nil, diceerrors.NewErrWithMessage("could not serialize result") + } + + return string(resultBytes), nil +} + +// Helper function to get the next argument safely. +func getNextArg(args []string) string { + if len(args) > 0 { + return args[0] + } + return "" +} + +func maxStrLen(arr []string) int { + maxLen := 0 + for _, str := range arr { + if len(str) > maxLen { + maxLen = len(str) + } + } + return maxLen +} + +// Define the constants +const ( + CmdArgNoEscape = "NOESCAPE" + CmdArgIndent = "INDENT" + CmdArgNewLine = "NEWLINE" + CmdArgSpace = "SPACE" + CmdArgFormat = "FORMAT" +) + +// Calculate the max length of JSON.GET subcommands. +var JSONGetSubCommandsMaxStrLen = maxStrLen([]string{ + CmdArgNoEscape, + CmdArgIndent, + CmdArgNewLine, + CmdArgSpace, + CmdArgFormat, +}) + +// helper function used by evalJSONGET and evalJSONMGET to prepare the results +func jsonGETHelper(store *dstore.Store, path, key string) (result interface{}, err2 []byte) { +>>>>>>> 7be9cf0 (Linking Commit to correct author) // Retrieve the object from the database obj := store.Get(key) if obj == nil {