diff --git a/integration_tests/commands/async/json_test.go b/integration_tests/commands/async/json_test.go index 10c4df024e..eab8386cc8 100644 --- a/integration_tests/commands/async/json_test.go +++ b/integration_tests/commands/async/json_test.go @@ -94,7 +94,7 @@ func TestJSONOperations(t *testing.T) { name: "Set Non-JSON Value", setCmd: `SET nonJson "not a json"`, getCmd: `JSON.GET nonJson`, - expected: "ERR Existing key has wrong Dice type", + expected: "Existing key has wrong Dice type", }, { name: "Set Empty JSON Object", @@ -1426,6 +1426,7 @@ func TestJsonSTRAPPEND(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { FireCommand(conn, "DEL doc") + defer FireCommand(conn, "DEL doc") result := FireCommand(conn, tc.setCmd) @@ -1435,5 +1436,4 @@ func TestJsonSTRAPPEND(t *testing.T) { assert.DeepEqual(t, tc.expected, result) }) } - } diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 17f0b76819..b15ac48094 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -25,6 +25,8 @@ const ( InternalServerError = "-ERR: Internal server error, unable to process command" InvalidFloatErr = "-ERR value is not a valid float" InvalidIntErr = "-ERR value is not a valid integer" + WrongKeyTypeErr = "-Existing key has wrong Dice type" + NoKeyExistsErr = "could not perform this operation on a key that doesn't exist" ) type DiceError struct { diff --git a/internal/eval/commands.go b/internal/eval/commands.go index ecd2bef29f..ddd25046fa 100644 --- a/internal/eval/commands.go +++ b/internal/eval/commands.go @@ -1017,7 +1017,7 @@ var ( Returns an array of integer replies for each path, the string's new length, or nil, if the matching JSON value is not a string. Error reply: If the value at path is not a string or if the key doesn't exist.`, Eval: evalJSONSTRAPPEND, - Arity: -3, + Arity: 3, KeySpecs: KeySpecs{BeginIndex: 1}, } ) diff --git a/internal/eval/dump_restore.go b/internal/eval/dump_restore.go index 1dedad0340..6b09ae329a 100644 --- a/internal/eval/dump_restore.go +++ b/internal/eval/dump_restore.go @@ -15,21 +15,21 @@ import ( ) func evalDUMP(args []string, store *dstore.Store) []byte { - if len(args) < 1 { - return diceerrors.NewErrArity("DUMP") - } - key := args[0] - obj := store.Get(key) - if obj == nil { - return diceerrors.NewErrWithFormattedMessage("nil") - } - - serializedValue, err := rdbSerialize(obj) - if err != nil { - return diceerrors.NewErrWithMessage("serialization failed") - } + if len(args) < 1 { + return diceerrors.NewErrArity("DUMP") + } + key := args[0] + obj := store.Get(key) + if obj == nil { + return diceerrors.NewErrWithFormattedMessage("nil") + } + + serializedValue, err := rdbSerialize(obj) + if err != nil { + return diceerrors.NewErrWithMessage("serialization failed") + } encodedResult := base64.StdEncoding.EncodeToString(serializedValue) - return clientio.Encode(encodedResult, false) + return clientio.Encode(encodedResult, false) } func evalRestore(args []string, store *dstore.Store) []byte { @@ -38,9 +38,9 @@ func evalRestore(args []string, store *dstore.Store) []byte { } key := args[0] - ttlStr:=args[1] + ttlStr := args[1] ttl, _ := strconv.ParseInt(ttlStr, 10, 64) - + encodedValue := args[2] serializedData, err := base64.StdEncoding.DecodeString(encodedValue) if err != nil { @@ -52,15 +52,15 @@ func evalRestore(args []string, store *dstore.Store) []byte { return diceerrors.NewErrWithMessage("deserialization failed: " + err.Error()) } - newobj:=store.NewObj(obj.Value,ttl,obj.TypeEncoding,obj.TypeEncoding) - var keepttl=true + newobj := store.NewObj(obj.Value, ttl, obj.TypeEncoding, obj.TypeEncoding) + var keepttl = true - if(ttl>0){ + if ttl > 0 { store.Put(key, newobj, dstore.WithKeepTTL(keepttl)) - }else{ - store.Put(key,obj) + } else { + store.Put(key, obj) } - + return clientio.RespOK } @@ -68,7 +68,7 @@ func rdbDeserialize(data []byte) (*object.Obj, error) { if len(data) < 3 { return nil, errors.New("insufficient data for deserialization") } - objType := data[1] + objType := data[1] switch objType { case 0x00: return readString(data[2:]) @@ -104,55 +104,55 @@ func readInt(data []byte) (*object.Obj, error) { } func rdbSerialize(obj *object.Obj) ([]byte, error) { - var buf bytes.Buffer - buf.WriteByte(0x09) - - switch object.GetType(obj.TypeEncoding) { - case object.ObjTypeString: - str, ok := obj.Value.(string) - if !ok { - return nil, errors.New("invalid string value") - } - buf.WriteByte(0x00) - if err := writeString(&buf, str); err != nil { - return nil, err - } - - case object.ObjTypeInt: - intVal, ok := obj.Value.(int64) - if !ok { - return nil, errors.New("invalid integer value") - } - buf.WriteByte(0xC0) - writeInt(&buf, intVal); - - default: - return nil, errors.New("unsupported object type") - } - - buf.WriteByte(0xFF) // End marker - - return appendChecksum(buf.Bytes()), nil + var buf bytes.Buffer + buf.WriteByte(0x09) + + switch object.GetType(obj.TypeEncoding) { + case object.ObjTypeString: + str, ok := obj.Value.(string) + if !ok { + return nil, errors.New("invalid string value") + } + buf.WriteByte(0x00) + if err := writeString(&buf, str); err != nil { + return nil, err + } + + case object.ObjTypeInt: + intVal, ok := obj.Value.(int64) + if !ok { + return nil, errors.New("invalid integer value") + } + buf.WriteByte(0xC0) + writeInt(&buf, intVal) + + default: + return nil, errors.New("unsupported object type") + } + + buf.WriteByte(0xFF) // End marker + + return appendChecksum(buf.Bytes()), nil } func writeString(buf *bytes.Buffer, str string) error { - strLen := uint32(len(str)) - if err := binary.Write(buf, binary.BigEndian, strLen); err != nil { - return err - } - buf.WriteString(str) - return nil + strLen := uint32(len(str)) + if err := binary.Write(buf, binary.BigEndian, strLen); err != nil { + return err + } + buf.WriteString(str) + return nil } -func writeInt(buf *bytes.Buffer, intVal int64){ - tempBuf := make([]byte, 8) - binary.BigEndian.PutUint64(tempBuf, uint64(intVal)) - buf.Write(tempBuf) +func writeInt(buf *bytes.Buffer, intVal int64) { + tempBuf := make([]byte, 8) + binary.BigEndian.PutUint64(tempBuf, uint64(intVal)) + buf.Write(tempBuf) } func appendChecksum(data []byte) []byte { - checksum := crc64.Checksum(data, crc64.MakeTable(crc64.ECMA)) - checksumBuf := make([]byte, 8) - binary.BigEndian.PutUint64(checksumBuf, checksum) - return append(data, checksumBuf...) + checksum := crc64.Checksum(data, crc64.MakeTable(crc64.ECMA)) + checksumBuf := make([]byte, 8) + binary.BigEndian.PutUint64(checksumBuf, checksum) + return append(data, checksumBuf...) } diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 1fb4742267..43c651fef7 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -4699,12 +4699,12 @@ func evalJSONSTRAPPEND(args []string, store *dstore.Store) []byte { obj := store.Get(key) if obj == nil { - return clientio.Encode([]interface{}{}, false) + return diceerrors.NewErrWithMessage(diceerrors.NoKeyExistsErr) } errWithMessage := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeJSON, object.ObjEncodingJSON) if errWithMessage != nil { - return errWithMessage + return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongKeyTypeErr) } jsonData := obj.Value @@ -4719,15 +4719,15 @@ func evalJSONSTRAPPEND(args []string, store *dstore.Store) []byte { resultsArray = append(resultsArray, int64(len(newValue))) jsonData = newValue } else { - return clientio.Encode([]interface{}{}, false) + return clientio.RespEmptyArray } } else { expr, err := jp.ParseString(path) if err != nil { - return clientio.Encode([]interface{}{}, false) + return clientio.RespEmptyArray } - newData, modifyErr := expr.Modify(jsonData, func(data any) (interface{}, bool) { + _, modifyErr := expr.Modify(jsonData, func(data any) (interface{}, bool) { switch v := data.(type) { case string: unquotedValue := strings.Trim(value, "\"") @@ -4741,12 +4741,14 @@ func evalJSONSTRAPPEND(args []string, store *dstore.Store) []byte { }) if modifyErr != nil { - return clientio.Encode([]interface{}{}, false) + return clientio.RespEmptyArray } - jsonData = newData } - store.Put(key, store.NewObj(jsonData, -1, object.ObjTypeJSON, object.ObjEncodingJSON)) + if len(resultsArray) == 0 { + return clientio.RespEmptyArray + } + obj.Value = jsonData return clientio.Encode(resultsArray, false) } diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 29b5586060..97289d2eaa 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -1494,7 +1494,7 @@ func testEvalJSONGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, input: []string{"EXISTING_KEY"}, - output: []byte("-ERR Existing key has wrong Dice type\r\n"), + output: []byte("-Existing key has wrong Dice type\r\n"), }, "key exists value": { setup: func() { @@ -2521,7 +2521,7 @@ func testEvalLLEN(t *testing.T, store *dstore.Store) { evalSET([]string{"EXISTING_KEY", "mock_value"}, store) }, input: []string{"EXISTING_KEY"}, - output: []byte("-ERR Existing key has wrong Dice type\r\n"), + output: []byte("-Existing key has wrong Dice type\r\n"), }, } @@ -4717,7 +4717,7 @@ func testEvalZADD(t *testing.T, store *dstore.Store) { store.Put("myzset", store.NewObj("string_value", -1, object.ObjTypeString, object.ObjEncodingRaw)) }, input: []string{"myzset", "1", "member1"}, - output: []byte("-ERR Existing key has wrong Dice type\r\n"), + output: []byte("-Existing key has wrong Dice type\r\n"), }, } @@ -5047,18 +5047,6 @@ func testEvalDUMP(t *testing.T, store *dstore.Store) { func testEvalJSONSTRAPPEND(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ - "append to multiple fields": { - setup: func() { - key := "doc1" - value := "{\"a\":\"foo\", \"nested1\": {\"a\": \"hello\"}, \"nested2\": {\"a\": 31}}" - var rootData interface{} - _ = sonic.Unmarshal([]byte(value), &rootData) - obj := store.NewObj(rootData, -1, object.ObjTypeJSON, object.ObjEncodingJSON) - store.Put(key, obj) - }, - input: []string{"doc1", "$..a", "\"bar\""}, - output: []byte("*3\r\n:6\r\n:8\r\n$-1\r\n"), // Cannot append to numeric field - }, "append to single field": { setup: func() { key := "doc1" @@ -5076,7 +5064,7 @@ func testEvalJSONSTRAPPEND(t *testing.T, store *dstore.Store) { // No setup needed as we are testing a non-existing document. }, input: []string{"non_existing_doc", "$..a", "\"err\""}, - output: []byte("*0\r\n"), // No results for non-existing key + output: []byte("-ERR could not perform this operation on a key that doesn't exist\r\n"), }, "legacy path append": { setup: func() { diff --git a/internal/object/typeencoding.go b/internal/object/typeencoding.go index 4acd0a7b43..619aa8d6db 100644 --- a/internal/object/typeencoding.go +++ b/internal/object/typeencoding.go @@ -30,10 +30,10 @@ func AssertEncoding(te, e uint8) error { func AssertTypeAndEncoding(typeEncoding, expectedType, expectedEncoding uint8) []byte { if err := AssertType(typeEncoding, expectedType); err != nil { - return diceerrors.NewErrWithMessage("Existing key has wrong Dice type") + return diceerrors.NewErrWithMessage(diceerrors.WrongKeyTypeErr) } if err := AssertEncoding(typeEncoding, expectedEncoding); err != nil { - return diceerrors.NewErrWithMessage("Existing key has wrong Dice type") + return diceerrors.NewErrWithMessage(diceerrors.WrongKeyTypeErr) } return nil }