From 08b65d7e76bac52e6f4405f887e407185b59a6b0 Mon Sep 17 00:00:00 2001 From: Harish <79055105+c-harish@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:14:46 +0530 Subject: [PATCH 01/20] docs : fix inconsistencies in hlen/hscan/hstrlen/zcard/zrem (#1218) --- docs/src/content/docs/commands/HLEN.md | 16 ++++++++-------- docs/src/content/docs/commands/HSCAN.md | 12 ++++++------ docs/src/content/docs/commands/HSTRLEN.md | 16 ++++++++-------- docs/src/content/docs/commands/ZCARD.md | 22 +++++++++++----------- docs/src/content/docs/commands/ZREM.md | 20 ++++++++++---------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/docs/src/content/docs/commands/HLEN.md b/docs/src/content/docs/commands/HLEN.md index 65d271201..c8ef24a03 100644 --- a/docs/src/content/docs/commands/HLEN.md +++ b/docs/src/content/docs/commands/HLEN.md @@ -50,30 +50,30 @@ HLEN key ### Basic Usage Creating hash `myhash` with two fields `field1` and `field2`. Getting hash length of `myhash`. -```DiceDB -> HSET myhash field1 "value1" field2 "value2" +```bash +127.0.0.1:7379> HSET myhash field1 "value1" field2 "value2" (integer) 2 -> HLEN myhash +127.0.0.1:7379> HLEN myhash (integer) 2 ``` ### Invalid Usage on non-existent key Getting hash length from a non-existent hash key `nonExistentHash`. -```DiceDB -> HLEN nonExistentHash +```bash +127.0.0.1:7379> HLEN nonExistentHash (integer) 0 ``` ### Invalid Usage on non-hash key Getting hash length from a key `mystring` associated with a non-hash type. -```DiceDB -> SET mystring "This is a string" +```bash +127.0.0.1:7379> SET mystring "This is a string" OK -> HLEN mystring +127.0.0.1:7379> HLEN mystring (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` diff --git a/docs/src/content/docs/commands/HSCAN.md b/docs/src/content/docs/commands/HSCAN.md index 5c46c6e0d..9177eea7a 100644 --- a/docs/src/content/docs/commands/HSCAN.md +++ b/docs/src/content/docs/commands/HSCAN.md @@ -51,31 +51,31 @@ The `HSCAN` command returns an array containing the next cursor and the matching Creating a hash `myhash` with two fields `field1` and `field2`. Getting `HSCAN` on `myhash` with valid cursors. ```bash -> HSET myhash field1 "value1" field2 "value2" +127.0.0.1:7379> HSET myhash field1 "value1" field2 "value2" 1) (integer) 2 -> HSCAN myhash 0 +127.0.0.1:7379> HSCAN myhash 0 1) "2" 2) 1) "field1" 2) "value1" 3) "field2" 4) "value2" -> HSCAN myhash 0 MATCH field* COUNT 1 +127.0.0.1:7379> HSCAN myhash 0 MATCH field* COUNT 1 1) "1" 2) 1) "field1" 2) "value1" -> HSCAN myhash 1 MATCH field* COUNT 1 +127.0.0.1:7379> HSCAN myhash 1 MATCH field* COUNT 1 1) "0" 2) 1) "field2" 2) "value2" ``` ### Invalid Usage on non-existent key -Getting `HSCAN` on nonExistentHash. +Getting `HSCAN` on `nonExistentHash`. ```bash -> HSCAN nonExistentHash 0 +127.0.0.1:7379> HSCAN nonExistentHash 0 1) "0" 2) (empty array) ``` diff --git a/docs/src/content/docs/commands/HSTRLEN.md b/docs/src/content/docs/commands/HSTRLEN.md index 35c6e56ad..a09279939 100644 --- a/docs/src/content/docs/commands/HSTRLEN.md +++ b/docs/src/content/docs/commands/HSTRLEN.md @@ -50,30 +50,30 @@ HSTRLEN key field ### Basic Usage Creating hash `myhash` with two fields `field1` and `field2`. Getting string length of value in `field1`. -```DiceDB -> HSET myhash field1 "helloworld" field2 "value2" +```bash +127.0.0.1:7379> HSET myhash field1 "helloworld" field2 "value2" (integer) 1 -> HSTRLEN myhash field1 +127.0.0.1:7379> HSTRLEN myhash field1 (integer) 10 ``` ### Invalid Usage on non-existent key Getting string length from a non-existent key `nonExistentHash`. -```DiceDB -> HSTRLEN nonExistentHash field1 +```bash +127.0.0.1:7379> HSTRLEN nonExistentHash field1 (integer) 0 ``` ### Invalid Usage on non-hash key Getting string length from a key `mystring` associated with a non-hash type. -```DiceDB -> SET mystring "This is a string" +```bash +127.0.0.1:7379> SET mystring "This is a string" OK -> HSTRLEN mystring field1 +127.0.0.1:7379> HSTRLEN mystring field1 (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` diff --git a/docs/src/content/docs/commands/ZCARD.md b/docs/src/content/docs/commands/ZCARD.md index 0e0c1bbb3..5f2f086db 100644 --- a/docs/src/content/docs/commands/ZCARD.md +++ b/docs/src/content/docs/commands/ZCARD.md @@ -50,36 +50,36 @@ ZCARD key ### Basic Usage Creating sorted set `myzset` with two fields `one`, `two` with scores 1, 2 respectively. Getting cardinality of `myzset`. Adding new element into `myzset` and getting updated cardinality. -```DiceDB -> ZADD myzset 1 "one" 2 "two" +```bash +127.0.0.1:7379> ZADD myzset 1 "one" 2 "two" (integer) 2 -> ZCARD myzset +127.0.0.1:7379> ZCARD myzset (integer) 2 -> ZADD myzset 3 "three" +127.0.0.1:7379> ZADD myzset 3 "three" (integer) 1 -> ZCARD myzset +127.0.0.1:7379> ZCARD myzset (integer) 3 ``` ### Invalid Usage on non-existent sorted set Getting cardinality of a non-existent sorted set `nonExistentZSet`. -```DiceDB -> ZCARD nonExistentZSet +```bash +127.0.0.1:7379> ZCARD nonExistentZSet (integer) 0 ``` ### Invalid Usage on a non sorted set key -Getting cardinality of a key `myzset` associated with a non sorted set type. +Getting cardinality of a key `mystring` associated with a non sorted set type. -```DiceDB -> SET mystring "This is a string" +```bash +127.0.0.1:7379> SET mystring "This is a string" OK -> ZCARD mystring +127.0.0.1:7379> ZCARD mystring (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` diff --git a/docs/src/content/docs/commands/ZREM.md b/docs/src/content/docs/commands/ZREM.md index 9621c1d02..44acc5d66 100644 --- a/docs/src/content/docs/commands/ZREM.md +++ b/docs/src/content/docs/commands/ZREM.md @@ -52,32 +52,32 @@ ZREM key member [member ...] ### Basic Usage Creating sorted set `myzset` with fields `one`, `two`, `three`, `four`, `five` with scores 1, 2, 3, 4, 5 respectively. Removing elements from `myzset`. -```DiceDB -> ZADD myzset 1 "one" 2 "two" 3 "three" 4 "four" 5 "five" +```bash +127.0.0.1:7379> ZADD myzset 1 "one" 2 "two" 3 "three" 4 "four" 5 "five" (integer) 5 -> ZREM myzset one +127.0.0.1:7379> ZREM myzset one (integer) 1 -> ZREM myzset two six +127.0.0.1:7379> ZREM myzset two six (integer) 1 -> ZREM myzset three four +127.0.0.1:7379> ZREM myzset three four (integer) 2 ``` ### Invalid Usage on non-existent sorted set Removing element from a non-existent sorted set `nonExistentZSet`. -```DiceDB -> ZREM nonExistentZSet one +```bash +127.0.0.1:7379> ZREM nonExistentZSet one (integer) 0 ``` ### Invalid Usage on a non sorted set key Getting cardinality of a key `mystring` associated with a non sorted set type. -```DiceDB -> SET mystring "This is a string" +```bash +127.0.0.1:7379> SET mystring "This is a string" OK -> ZREM mystring +127.0.0.1:7379> ZREM mystring (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` From adf1df56075a37954f60cdb5437c860e5143895e Mon Sep 17 00:00:00 2001 From: Vansh Chopra <76000026+vanshavenger@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:16:05 +0530 Subject: [PATCH 02/20] FIX: SET Command Inconsistency (#1215) --- integration_tests/commands/async/set_test.go | 5 +- integration_tests/commands/http/set_test.go | 88 +++++ integration_tests/commands/resp/set_test.go | 5 +- .../commands/websocket/set_test.go | 5 +- internal/eval/eval_test.go | 45 +++ internal/eval/store_eval.go | 306 +++++++++--------- 6 files changed, 304 insertions(+), 150 deletions(-) diff --git a/integration_tests/commands/async/set_test.go b/integration_tests/commands/async/set_test.go index 18476a3de..fe9710e42 100644 --- a/integration_tests/commands/async/set_test.go +++ b/integration_tests/commands/async/set_test.go @@ -169,12 +169,13 @@ func TestSetWithExat(t *testing.T) { func TestWithKeepTTLFlag(t *testing.T) { conn := getLocalConnection() + expiryTime := strconv.FormatInt(time.Now().Add(1*time.Minute).UnixMilli(), 10) defer conn.Close() for _, tcase := range []TestCase{ { - commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk"}, - expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv"}, + commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk", "SET K V EX 2 KEEPTTL", "SET K1 vv PX 2000 KEEPTTL", "SET K2 vv EXAT " + expiryTime + " KEEPTTL"}, + expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv", "ERR syntax error", "ERR syntax error", "ERR syntax error"}, }, } { for i := 0; i < len(tcase.commands); i++ { diff --git a/integration_tests/commands/http/set_test.go b/integration_tests/commands/http/set_test.go index 259bf2ac5..5e5d0d13d 100644 --- a/integration_tests/commands/http/set_test.go +++ b/integration_tests/commands/http/set_test.go @@ -203,6 +203,94 @@ func TestSetWithOptions(t *testing.T) { } } +func TestWithKeepTTLFlag(t *testing.T) { + exec := NewHTTPCommandExecutor() + expiryTime := strconv.FormatInt(time.Now().Add(1*time.Minute).UnixMilli(), 10) + + testCases := []TestCase { + { + name: "SET WITH KEEP TTL", + commands: []HTTPCommand{ + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "ex": 3}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v2", "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"OK", "v", "OK", "v2"}, + }, + { + name: "SET WITH KEEP TTL on non-existing key", + commands: []HTTPCommand{ + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"OK", "v"}, + }, + { + name: "SET WITH KEEPTTL with PX", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "px": 2000, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"ERR syntax error", nil}, + }, + { + name: "SET WITH KEEPTTL with EX", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "ex": 3, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"ERR syntax error", nil}, + }, + { + name: "SET WITH KEEPTTL with NX", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "nx": true, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"OK", "v"}, + }, + { + name: "SET WITH KEEPTTL with XX", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "xx": true, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{nil, nil}, + }, + { + name: "SET WITH KEEPTTL with PXAT", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "pxat": expiryTime, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"ERR syntax error", nil}, + }, + { + + name: "SET WITH KEEPTTL with EXAT", + commands: []HTTPCommand { + {Command: "SET", Body: map[string]interface{}{"key": "k", "value": "v", "exat": expiryTime, "keepttl": true}}, + {Command: "GET", Body: map[string]interface{}{"key": "k"}}, + }, + expected: []interface{}{"ERR syntax error", nil}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "k"}}) + exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "k1"}}) + exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"key": "k2"}}) + for i, cmd := range tc.commands { + result, _ := exec.FireCommand(cmd) + assert.Equal(t, tc.expected[i], result) + } + }) + } + +} + func TestSetWithExat(t *testing.T) { exec := NewHTTPCommandExecutor() Etime := strconv.FormatInt(time.Now().Unix()+5, 10) diff --git a/integration_tests/commands/resp/set_test.go b/integration_tests/commands/resp/set_test.go index e6fa927c3..1c015ff5a 100644 --- a/integration_tests/commands/resp/set_test.go +++ b/integration_tests/commands/resp/set_test.go @@ -176,12 +176,13 @@ func TestSetWithExat(t *testing.T) { func TestWithKeepTTLFlag(t *testing.T) { conn := getLocalConnection() + expiryTime := strconv.FormatInt(time.Now().Add(1*time.Minute).UnixMilli(), 10) defer conn.Close() for _, tcase := range []TestCase{ { - commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk"}, - expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv"}, + commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk", "SET K V EX 2 KEEPTTL", "SET K1 vv PX 2000 KEEPTTL", "SET K2 vv EXAT " + expiryTime + " KEEPTTL"}, + expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv", "ERR syntax error", "ERR syntax error", "ERR syntax error"}, }, } { for i := 0; i < len(tcase.commands); i++ { diff --git a/integration_tests/commands/websocket/set_test.go b/integration_tests/commands/websocket/set_test.go index ec77008dd..94480ebde 100644 --- a/integration_tests/commands/websocket/set_test.go +++ b/integration_tests/commands/websocket/set_test.go @@ -223,12 +223,13 @@ func TestSetWithExat(t *testing.T) { func TestWithKeepTTLFlag(t *testing.T) { exec := NewWebsocketCommandExecutor() + expiryTime := strconv.FormatInt(time.Now().Add(1*time.Minute).UnixMilli(), 10) conn := exec.ConnectToServer() for _, tcase := range []TestCase{ { - commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk"}, - expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv"}, + commands: []string{"SET k v EX 2", "SET k vv KEEPTTL", "GET k", "SET kk vv", "SET kk vvv KEEPTTL", "GET kk", "SET K V EX 2 KEEPTTL", "SET K1 vv PX 2000 KEEPTTL", "SET K2 vv EXAT " + expiryTime + " KEEPTTL"}, + expected: []interface{}{"OK", "OK", "vv", "OK", "OK", "vvv", "ERR syntax error", "ERR syntax error", "ERR syntax error"}, }, } { for i := 0; i < len(tcase.commands); i++ { diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 2b10d5c02..069c5d435 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -307,6 +307,51 @@ func testEvalSET(t *testing.T, store *dstore.Store) { input: []string{"KEY", "VAL", Pxat, strconv.FormatInt(time.Now().Add(2*time.Minute).UnixMilli(), 10)}, migratedOutput: EvalResponse{Result: clientio.OK, Error: nil}, }, + { + name: "key val pair and invalid EX and PX", + input: []string{"KEY", "VAL", Ex, "2", Px, "2000"}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and invalid EX and PXAT", + input: []string{"KEY", "VAL", Ex, "2", Pxat, "2"}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and invalid PX and PXAT", + input: []string{"KEY", "VAL", Px, "2000", Pxat, "2"}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and KeepTTL", + input: []string{"KEY", "VAL", KeepTTL}, + migratedOutput: EvalResponse{Result: clientio.OK, Error: nil}, + }, + { + name: "key val pair and invalid KeepTTL", + input: []string{"KEY", "VAL", KeepTTL, "2"}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and KeepTTL, EX", + input: []string{"KEY", "VAL", Ex, "2", KeepTTL}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and KeepTTL, PX", + input: []string{"KEY", "VAL", Px, "2000", KeepTTL}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and KeepTTL, PXAT", + input: []string{"KEY", "VAL", Pxat, strconv.FormatInt(time.Now().Add(2*time.Minute).UnixMilli(), 10), KeepTTL}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, + }, + { + name: "key val pair and KeepTTL, invalid PXAT", + input: []string{"KEY", "VAL", Pxat, "invalid_expiry_val", KeepTTL}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR value is not an integer or out of range")}, + }, } for _, tt := range tests { diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index d9c90719b..892ed0d18 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -36,151 +36,169 @@ import ( // Returns encoded OK RESP once new entry is added // If the key already exists then the value will be overwritten and expiry will be discarded func evalSET(args []string, store *dstore.Store) *EvalResponse { - if len(args) <= 1 { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongArgumentCount("SET"), - } - } - - var key, value string - var exDurationMs int64 = -1 - var state exDurationState = Uninitialized - var keepttl bool = false - + if len(args) <= 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("SET"), + } + } + + var key, value string + var exDurationMs int64 = -1 + var state exDurationState = Uninitialized + var keepttl bool = false + key, value = args[0], args[1] - oType, oEnc := deduceTypeEncoding(value) - - for i := 2; i < len(args); i++ { - arg := strings.ToUpper(args[i]) - switch arg { - case Ex, Px: - if state != Uninitialized { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - i++ - if i == len(args) { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrIntegerOutOfRange, - } - } - - if exDuration <= 0 || exDuration >= maxExDuration { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrInvalidExpireTime("SET"), - } - } - - // converting seconds to milliseconds - if arg == Ex { - exDuration *= 1000 - } - exDurationMs = exDuration - state = Initialized - - case Pxat, Exat: - if state != Uninitialized { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - i++ - if i == len(args) { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrIntegerOutOfRange, - } - } - - if exDuration < 0 { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrInvalidExpireTime("SET"), - } - } - - if arg == Exat { - exDuration *= 1000 - } - exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() - // If the expiry time is in the past, set exDurationMs to 0 - // This will be used to signal immediate expiration - if exDurationMs < 0 { - exDurationMs = 0 - } - state = Initialized - - case XX: - // Get the key from the hash table - obj := store.Get(key) - - // if key does not exist, return RESP encoded nil - if obj == nil { - return &EvalResponse{ - Result: clientio.NIL, - Error: nil, - } - } - case NX: - obj := store.Get(key) - if obj != nil { - return &EvalResponse{ - Result: clientio.NIL, - Error: nil, - } - } - case KeepTTL: - keepttl = true - default: - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - } - - // Cast the value properly based on the encoding type - var storedValue interface{} - switch oEnc { - case object.ObjEncodingInt: - storedValue, _ = strconv.ParseInt(value, 10, 64) - case object.ObjEncodingEmbStr, object.ObjEncodingRaw: - storedValue = value - default: - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrUnsupportedEncoding(int(oEnc)), - } - } - - // putting the k and value in a Hash Table - store.Put(key, store.NewObj(storedValue, exDurationMs, oType, oEnc), dstore.WithKeepTTL(keepttl)) - - return &EvalResponse{ - Result: clientio.OK, - Error: nil, - } + oType, oEnc := deduceTypeEncoding(value) + + for i := 2; i < len(args); i++ { + arg := strings.ToUpper(args[i]) + switch arg { + case Ex, Px: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + if keepttl { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDuration <= 0 || exDuration >= maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("SET"), + } + } + + // converting seconds to milliseconds + if arg == Ex { + exDuration *= 1000 + } + exDurationMs = exDuration + state = Initialized + + case Pxat, Exat: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + if keepttl { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDuration < 0 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("SET"), + } + } + + if arg == Exat { + exDuration *= 1000 + } + exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() + // If the expiry time is in the past, set exDurationMs to 0 + // This will be used to signal immediate expiration + if exDurationMs < 0 { + exDurationMs = 0 + } + state = Initialized + + case XX: + // Get the key from the hash table + obj := store.Get(key) + + // if key does not exist, return RESP encoded nil + if obj == nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + case NX: + obj := store.Get(key) + if obj != nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + case KeepTTL: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + keepttl = true + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + } + + // Cast the value properly based on the encoding type + var storedValue interface{} + switch oEnc { + case object.ObjEncodingInt: + storedValue, _ = strconv.ParseInt(value, 10, 64) + case object.ObjEncodingEmbStr, object.ObjEncodingRaw: + storedValue = value + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnsupportedEncoding(int(oEnc)), + } + } + + // putting the k and value in a Hash Table + store.Put(key, store.NewObj(storedValue, exDurationMs, oType, oEnc), dstore.WithKeepTTL(keepttl)) + + return &EvalResponse{ + Result: clientio.OK, + Error: nil, + } } // evalGET returns the value for the queried key in args From 3a36c77373680cc55e0800e544ab7472100b0d68 Mon Sep 17 00:00:00 2001 From: Apoorv Yadav <32174554+apoorvyadav1111@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:17:55 +0530 Subject: [PATCH 03/20] #1117: Addressed review comments (#1186) --- internal/eval/eval.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 19f9b182e..f0370fbbb 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -53,7 +53,16 @@ type EvalResponse struct { Error error // Error holds any error that occurred during the operation. If no error, it will be nil. } -//go:inline +// Following functions should be used to create a new EvalResponse with the given result and error. +// These ensure that result and error are mutually exclusive. +// If result is nil, then error should be non-nil and vice versa. + +// makeEvalResult creates a new EvalResponse with the given result and nil error. +// This is a helper function to create a new EvalResponse with the given result and nil error. +/** + * @param {interface{}} result - The result of the store operation. + * @returns {EvalResponse} A new EvalResponse with the given result and nil error. + */ func makeEvalResult(result interface{}) *EvalResponse { return &EvalResponse{ Result: result, @@ -61,7 +70,12 @@ func makeEvalResult(result interface{}) *EvalResponse { } } -//go:inline +// makeEvalError creates a new EvalResponse with the given error and nil result. +// This is a helper function to create a new EvalResponse with the given error and nil result. +/** + * @param {error} err - The error that occurred during the store operation. + * @returns {EvalResponse} A new EvalResponse with the given error and nil result. + */ func makeEvalError(err error) *EvalResponse { return &EvalResponse{ Result: nil, From a4a3f5730b90b9a9be526b6ae0695c45b9eecd77 Mon Sep 17 00:00:00 2001 From: Ashwin Kulkarni Date: Wed, 30 Oct 2024 19:14:25 +0530 Subject: [PATCH 04/20] Added startup splash and info table (#1217) * added startup splash and info table * Update main.go * more refactoring --- go.mod | 14 ++++---- go.sum | 28 +++++++-------- main.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 112 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index dd1f08f29..145a914ac 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require gotest.tools/v3 v3.5.1 require ( - github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/bytedance/sonic/loader v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -28,9 +28,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.10.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -48,7 +48,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/mmcloughlin/geohash v0.10.0 - github.com/ohler55/ojg v1.24.0 + github.com/ohler55/ojg v1.25.0 github.com/pelletier/go-toml/v2 v2.2.3 github.com/rs/xid v1.6.0 github.com/rs/zerolog v1.33.0 @@ -56,6 +56,6 @@ require ( github.com/stretchr/testify v1.9.0 github.com/twmb/murmur3 v1.1.8 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 - golang.org/x/crypto v0.27.0 - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + golang.org/x/crypto v0.28.0 + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c ) diff --git a/go.sum b/go.sum index 68fe43ac1..662ea7137 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0 github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= @@ -69,8 +69,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE= github.com/mmcloughlin/geohash v0.10.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c= -github.com/ohler55/ojg v1.24.0 h1:y2AVez6fPTszK/jPhaAYMCAzAoSleConMqSDD5wJKJg= -github.com/ohler55/ojg v1.24.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o= +github.com/ohler55/ojg v1.25.0 h1:sDwc4u4zex65Uz5Nm7O1QwDKTT+YRcpeZQTy1pffRkw= +github.com/ohler55/ojg v1.25.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -117,20 +117,20 @@ github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryB github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= -golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main.go b/main.go index 755a09231..f057069e4 100644 --- a/main.go +++ b/main.go @@ -12,12 +12,13 @@ import ( "runtime" "runtime/pprof" "runtime/trace" + "strings" "sync" "syscall" - "github.com/dicedb/dice/internal/watchmanager" "github.com/dicedb/dice/internal/logger" "github.com/dicedb/dice/internal/server/abstractserver" + "github.com/dicedb/dice/internal/watchmanager" "github.com/dicedb/dice/config" diceerrors "github.com/dicedb/dice/internal/errors" @@ -29,6 +30,13 @@ import ( "github.com/dicedb/dice/internal/worker" ) +type configEntry struct { + Key string + Value interface{} +} + +var configTable = []configEntry{} + func init() { flag.StringVar(&config.Host, "host", "0.0.0.0", "host for the DiceDB server") @@ -66,24 +74,91 @@ func init() { slog.SetDefault(logger.New()) } -func main() { +func printSplash() { fmt.Print(` -██████╗ ██╗ ██████╗███████╗██████╗ ██████╗ -██╔══██╗██║██╔════╝██╔════╝██╔══██╗██╔══██╗ -██║ ██║██║██║ █████╗ ██║ ██║██████╔╝ -██║ ██║██║██║ ██╔══╝ ██║ ██║██╔══██╗ -██████╔╝██║╚██████╗███████╗██████╔╝██████╔╝ -╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═════╝ - -`) - slog.Info("starting DiceDB", slog.String("version", config.DiceDBVersion)) - slog.Info("running with", slog.Int("port", config.Port)) - slog.Info("running with", slog.Bool("enable-watch", config.EnableWatch)) - - if config.EnableProfiling { - slog.Info("running with", slog.Bool("enable-profiling", config.EnableProfiling)) + ██████╗ ██╗ ██████╗███████╗██████╗ ██████╗ + ██╔══██╗██║██╔════╝██╔════╝██╔══██╗██╔══██╗ + ██║ ██║██║██║ █████╗ ██║ ██║██████╔╝ + ██║ ██║██║██║ ██╔══╝ ██║ ██║██╔══██╗ + ██████╔╝██║╚██████╗███████╗██████╔╝██████╔╝ + ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═════╝ + + `) +} + +// configuration function used to add configuration values to the print table at the startup. +// add entry to this function to add a new row in the startup configuration table. +func configuration() { + // Add the version of the DiceDB to the configuration table + addEntry("Version", config.DiceDBVersion) + + // Add the port number on which DiceDB is running to the configuration table + addEntry("Port", config.Port) + + // Add whether multi-threading is enabled to the configuration table + addEntry("Multi Threading Enabled", config.EnableMultiThreading) + + // Add the number of CPU cores available on the machine to the configuration table + addEntry("Cores", runtime.NumCPU()) + + // Conditionally add the number of shards to be used for DiceDB to the configuration table + if config.NumShards > 0 { + configTable = append(configTable, configEntry{"Shards", config.NumShards}) + } else { + configTable = append(configTable, configEntry{"Shards", runtime.NumCPU()}) } + // Add whether the watch feature is enabled to the configuration table + addEntry("Watch Enabled", config.EnableWatch) + + // Add whether the watch feature is enabled to the configuration table + addEntry("HTTP Enabled", config.EnableHTTP) + + // Add whether the watch feature is enabled to the configuration table + addEntry("Websocket Enabled", config.EnableWebsocket) +} + +func addEntry(k string, v interface{}) { + configTable = append(configTable, configEntry{k, v}) +} + +// printConfigTable prints key-value pairs in a vertical table format. +func printConfigTable() { + configuration() + + // Find the longest key to align the values properly + maxKeyLength := 0 + maxValueLength := 20 // Default value length for alignment + for _, entry := range configTable { + if len(entry.Key) > maxKeyLength { + maxKeyLength = len(entry.Key) + } + if len(fmt.Sprintf("%v", entry.Value)) > maxValueLength { + maxValueLength = len(fmt.Sprintf("%v", entry.Value)) + } + } + + // Create the table header and separator line + fmt.Println() + totalWidth := maxKeyLength + maxValueLength + 7 // 7 is for spacing and pipes + fmt.Println(strings.Repeat("-", totalWidth)) + fmt.Printf("| %-*s | %-*s |\n", maxKeyLength, "Configuration", maxValueLength, "Value") + fmt.Println(strings.Repeat("-", totalWidth)) + + // Print each configuration key-value pair without row lines + for _, entry := range configTable { + fmt.Printf("| %-*s | %-20v |\n", maxKeyLength, entry.Key, entry.Value) + } + + // Final bottom line + fmt.Println(strings.Repeat("-", totalWidth)) + fmt.Println() +} + +func main() { + printSplash() + printConfigTable() + go observability.Ping() ctx, cancel := context.WithCancel(context.Background()) @@ -116,10 +191,8 @@ func main() { if config.NumShards > 0 { numShards = config.NumShards } - slog.Info("running with", slog.String("mode", "multi-threaded"), slog.Int("num-shards", numShards)) } else { numShards = 1 - slog.Info("running with", slog.String("mode", "single-threaded")) } // The runtime.GOMAXPROCS(numShards) call limits the number of operating system From e6f2c43501b15e1a40c74967aac6e62514db4ab7 Mon Sep 17 00:00:00 2001 From: Ashwin Kulkarni Date: Wed, 30 Oct 2024 19:38:26 +0530 Subject: [PATCH 05/20] Config Table print fix for single threading num shards (#1221) * removed unwanted goroutine * made errors lowercase to match with the existing scenario of redis * added startup splash and info table * Update main.go * more refactoring * fixed printing of num shards in case of single threading --------- Co-authored-by: Ashwin Arvind Kulkarni --- main.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index f057069e4..e11a66479 100644 --- a/main.go +++ b/main.go @@ -102,10 +102,14 @@ func configuration() { addEntry("Cores", runtime.NumCPU()) // Conditionally add the number of shards to be used for DiceDB to the configuration table - if config.NumShards > 0 { - configTable = append(configTable, configEntry{"Shards", config.NumShards}) + if config.EnableMultiThreading { + if config.NumShards > 0 { + configTable = append(configTable, configEntry{"Shards", config.NumShards}) + } else { + configTable = append(configTable, configEntry{"Shards", runtime.NumCPU()}) + } } else { - configTable = append(configTable, configEntry{"Shards", runtime.NumCPU()}) + configTable = append(configTable, configEntry{"Shards", 1}) } // Add whether the watch feature is enabled to the configuration table From fb61f0aa5410bdaadad383b85f6227c1a77c1e19 Mon Sep 17 00:00:00 2001 From: King Date: Wed, 30 Oct 2024 22:21:01 +0100 Subject: [PATCH 06/20] #606 Feat/test suite migration (#1220) --- .../commands/async/bit_operation_test.go | 2 +- .../commands/async/bit_ops_string_int_test.go | 4 +- .../commands/async/bitfield_test.go | 6 +- .../commands/async/check_type_test.go | 2 +- .../commands/async/command_count_test.go | 4 +- .../commands/async/command_default_test.go | 6 +- .../commands/async/command_getkeys_test.go | 4 +- .../commands/async/command_info_test.go | 4 +- .../commands/async/command_list_test.go | 4 +- .../commands/async/command_rename_test.go | 2 +- integration_tests/commands/async/copy_test.go | 5 +- .../commands/async/dbsize_test.go | 2 +- integration_tests/commands/async/del_test.go | 2 +- .../commands/async/deque_test.go | 2 +- .../commands/async/discard_test.go | 2 +- integration_tests/commands/async/dump_test.go | 10 +- integration_tests/commands/async/echo_test.go | 2 +- .../commands/async/exists_test.go | 2 +- .../commands/async/expire_test.go | 6 +- .../commands/async/expireat_test.go | 6 +- .../commands/async/expiretime_test.go | 6 +- .../commands/async/flushdb_test.go | 2 +- integration_tests/commands/async/get_test.go | 2 +- .../commands/async/getdel_test.go | 2 +- .../commands/async/getex_test.go | 6 +- .../commands/async/getset_test.go | 2 +- integration_tests/commands/async/hdel_test.go | 4 +- .../commands/async/hello_test.go | 4 +- integration_tests/commands/async/hget_test.go | 4 +- .../commands/async/hgetall_test.go | 4 +- .../commands/async/hmget_test.go | 4 +- .../commands/async/hmset_test.go | 4 +- integration_tests/commands/async/hset_test.go | 4 +- .../commands/async/hsetnx_test.go | 4 +- .../commands/async/json_arrpop_test.go | 7 +- integration_tests/commands/async/json_test.go | 40 ++-- .../commands/async/jsonresp_test.go | 7 +- integration_tests/commands/async/keys_test.go | 6 +- integration_tests/commands/async/mget_test.go | 6 +- integration_tests/commands/async/mset_test.go | 6 +- .../commands/async/object_test.go | 6 +- .../commands/async/qunwatch_test.go | 18 +- .../commands/async/qwatch_test.go | 46 ++-- .../commands/async/select_test.go | 6 +- .../commands/async/set_data_cmd_test.go | 8 +- .../commands/async/toggle_test.go | 12 +- .../commands/async/touch_test.go | 6 +- .../commands/async/ttl_pttl_test.go | 6 +- integration_tests/commands/async/type_test.go | 2 +- .../commands/http/append_test.go | 4 +- integration_tests/commands/http/bit_test.go | 4 +- integration_tests/commands/http/bloom_test.go | 2 +- .../commands/http/check_type_test.go | 2 +- .../commands/http/command_count_test.go | 6 +- .../commands/http/command_default_test.go | 6 +- .../commands/http/command_getkeys_test.go | 4 +- .../commands/http/command_help_test.go | 4 +- .../commands/http/command_info_test.go | 4 +- .../commands/http/command_list_test.go | 6 +- .../commands/http/command_rename_test.go | 4 +- integration_tests/commands/http/copy_test.go | 10 +- .../commands/http/countminsketch_test.go | 2 +- .../commands/http/dbsize_test.go | 4 +- integration_tests/commands/http/decr_test.go | 2 +- integration_tests/commands/http/del_test.go | 4 +- integration_tests/commands/http/deque_test.go | 18 +- integration_tests/commands/http/echo_test.go | 4 +- .../commands/http/exists_test.go | 4 +- .../commands/http/expire_test.go | 6 +- .../commands/http/expireat_test.go | 6 +- .../commands/http/expiretime_test.go | 6 +- integration_tests/commands/http/get_test.go | 2 +- .../commands/http/getdel_test.go | 2 +- integration_tests/commands/http/getex_test.go | 8 +- .../commands/http/getrange_test.go | 4 +- .../commands/http/getset_test.go | 2 +- .../commands/http/hincrby_test.go | 2 +- .../commands/http/hincrbyfloat_test.go | 2 +- integration_tests/commands/http/hlen_test.go | 6 +- .../commands/http/hrandfield_test.go | 6 +- integration_tests/commands/http/hscan_test.go | 6 +- .../commands/http/hsetnx_test.go | 2 +- .../commands/http/hstrlen_test.go | 2 +- .../commands/http/hyperloglog_test.go | 2 +- .../commands/http/incr_by_float_test.go | 2 +- integration_tests/commands/http/incr_test.go | 2 +- .../commands/http/json_arrpop_test.go | 9 +- integration_tests/commands/http/json_test.go | 68 +++--- integration_tests/commands/http/keys_test.go | 6 +- integration_tests/commands/http/mget_test.go | 6 +- integration_tests/commands/http/mset_test.go | 4 +- .../commands/http/object_test.go | 6 +- .../commands/http/set_data_cmd_test.go | 6 +- integration_tests/commands/http/set_test.go | 8 +- .../commands/http/toggle_test.go | 12 +- integration_tests/commands/http/touch_test.go | 2 +- .../commands/http/ttl_pttl_test.go | 6 +- integration_tests/commands/http/type_test.go | 2 +- integration_tests/commands/http/zcard_test.go | 6 +- integration_tests/commands/http/zrem_test.go | 6 +- .../commands/resp/append_test.go | 4 +- integration_tests/commands/resp/bloom_test.go | 2 +- .../commands/resp/command_docs_test.go | 4 +- .../commands/resp/command_getkeys_test.go | 4 +- .../commands/resp/command_info_test.go | 4 +- .../commands/resp/countminsketch_test.go | 10 +- integration_tests/commands/resp/get_test.go | 2 +- .../commands/resp/getrange_test.go | 4 +- .../commands/resp/getset_test.go | 2 +- .../commands/resp/getwatch_test.go | 20 +- .../commands/resp/hincrby_test.go | 2 +- .../commands/resp/hincrbyfloat_test.go | 2 +- integration_tests/commands/resp/hlen_test.go | 4 +- .../commands/resp/hrandfield_test.go | 6 +- integration_tests/commands/resp/hscan_test.go | 4 +- .../commands/resp/hstrlen_test.go | 4 +- .../commands/resp/hyperloglog_test.go | 4 +- .../commands/resp/incr_by_float_test.go | 2 +- integration_tests/commands/resp/incr_test.go | 2 +- integration_tests/commands/resp/json_test.go | 49 +++-- integration_tests/commands/resp/set_test.go | 8 +- .../commands/websocket/hlen_test.go | 6 +- .../commands/websocket/hscan_test.go | 6 +- .../commands/websocket/hstrlen_test.go | 6 +- .../commands/websocket/json_test.go | 107 ++++----- .../commands/websocket/writeretry_test.go | 17 +- .../commands/websocket/zcard_test.go | 6 +- .../commands/websocket/zrem_test.go | 6 +- integration_tests/server/max_conn_test.go | 2 +- internal/clientio/io_test.go | 2 +- internal/clientio/resp_test.go | 6 +- .../dencoding/dencoding_benchmark_test.go | 2 +- internal/dencoding/int_test.go | 6 +- internal/eval/bloom_test.go | 2 +- internal/eval/bytearray_test.go | 6 +- internal/eval/bytelist_test.go | 6 +- internal/eval/deque_test.go | 2 +- internal/eval/eval_test.go | 207 +++++++++--------- internal/server/utils/jsontype_test.go | 2 +- internal/sql/dsql_test.go | 40 ++-- internal/sql/executor_test.go | 166 +++++++------- internal/sql/fingerprint_test.go | 10 +- 142 files changed, 703 insertions(+), 673 deletions(-) diff --git a/integration_tests/commands/async/bit_operation_test.go b/integration_tests/commands/async/bit_operation_test.go index 512109b92..959745f58 100644 --- a/integration_tests/commands/async/bit_operation_test.go +++ b/integration_tests/commands/async/bit_operation_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestBitOp(t *testing.T) { diff --git a/integration_tests/commands/async/bit_ops_string_int_test.go b/integration_tests/commands/async/bit_ops_string_int_test.go index 94c1c0e75..13379fdfc 100644 --- a/integration_tests/commands/async/bit_ops_string_int_test.go +++ b/integration_tests/commands/async/bit_ops_string_int_test.go @@ -5,7 +5,7 @@ import ( "math/rand" "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestBitOpsString(t *testing.T) { @@ -180,7 +180,7 @@ func TestBitOpsString(t *testing.T) { case "equal": assert.Equal(t, res, tc.expected[i]) case "less": - assert.Assert(t, res.(int64) <= tc.expected[i].(int64), "CMD: %s Expected %d to be less than or equal to %d", tc.cmds[i], res, tc.expected[i]) + assert.True(t, res.(int64) <= tc.expected[i].(int64), "CMD: %s Expected %d to be less than or equal to %d", tc.cmds[i], res, tc.expected[i]) } } }) diff --git a/integration_tests/commands/async/bitfield_test.go b/integration_tests/commands/async/bitfield_test.go index 0beb9139d..4664e8ebe 100644 --- a/integration_tests/commands/async/bitfield_test.go +++ b/integration_tests/commands/async/bitfield_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestBitfield(t *testing.T) { @@ -245,7 +245,7 @@ func TestBitfield(t *testing.T) { } result := FireCommand(conn, tc.Commands[i]) expected := tc.Expected[i] - testifyAssert.Equal(t, expected, result) + assert.Equal(t, expected, result) } for _, cmd := range tc.CleanUp { @@ -366,7 +366,7 @@ func TestBitfieldRO(t *testing.T) { } result := FireCommand(conn, tc.Commands[i]) expected := tc.Expected[i] - testifyAssert.Equal(t, expected, result) + assert.Equal(t, expected, result) } for _, cmd := range tc.CleanUp { diff --git a/integration_tests/commands/async/check_type_test.go b/integration_tests/commands/async/check_type_test.go index 86f710f5c..eca785760 100644 --- a/integration_tests/commands/async/check_type_test.go +++ b/integration_tests/commands/async/check_type_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) // this file may contain test cases for checking error messages across all commands diff --git a/integration_tests/commands/async/command_count_test.go b/integration_tests/commands/async/command_count_test.go index 639f50bd6..9f7e33673 100644 --- a/integration_tests/commands/async/command_count_test.go +++ b/integration_tests/commands/async/command_count_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandCount(t *testing.T) { @@ -14,7 +14,7 @@ func TestCommandCount(t *testing.T) { t.Run("Command count should be positive", func(t *testing.T) { commandCount := getCommandCount(conn) - assert.Assert(t, commandCount > 0, + assert.True(t, commandCount > 0, fmt.Sprintf("Unexpected number of CLI commands found. expected greater than 0, %d found", commandCount)) }) } diff --git a/integration_tests/commands/async/command_default_test.go b/integration_tests/commands/async/command_default_test.go index 757404ef6..f5ca6e542 100644 --- a/integration_tests/commands/async/command_default_test.go +++ b/integration_tests/commands/async/command_default_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/dicedb/dice/internal/eval" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandDefault(t *testing.T) { @@ -15,12 +15,12 @@ func TestCommandDefault(t *testing.T) { commands := getCommandDefault(conn) t.Run("Command should not be empty", func(t *testing.T) { - assert.Assert(t, len(commands) > 0, + assert.True(t, len(commands) > 0, fmt.Sprintf("Unexpected number of CLI commands found. expected greater than 0, %d found", len(commands))) }) t.Run("Command count matches", func(t *testing.T) { - assert.Assert(t, len(commands) == len(eval.DiceCmds), + assert.True(t, len(commands) == len(eval.DiceCmds), fmt.Sprintf("Unexpected number of CLI commands found. expected %d, %d found", len(eval.DiceCmds), len(commands))) }) } diff --git a/integration_tests/commands/async/command_getkeys_test.go b/integration_tests/commands/async/command_getkeys_test.go index 0c2628291..0a7d94f65 100644 --- a/integration_tests/commands/async/command_getkeys_test.go +++ b/integration_tests/commands/async/command_getkeys_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var getKeysTestCases = []struct { @@ -31,7 +31,7 @@ func TestCommandGetKeys(t *testing.T) { for _, tc := range getKeysTestCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, "COMMAND GETKEYS "+tc.inCmd) - assert.DeepEqual(t, tc.expected, result) + assert.Equal(t, tc.expected, result) }) } } diff --git a/integration_tests/commands/async/command_info_test.go b/integration_tests/commands/async/command_info_test.go index 7e3e8ce64..4b58576f4 100644 --- a/integration_tests/commands/async/command_info_test.go +++ b/integration_tests/commands/async/command_info_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var getInfoTestCases = []struct { @@ -32,7 +32,7 @@ func TestCommandInfo(t *testing.T) { for _, tc := range getInfoTestCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, "COMMAND INFO "+tc.inCmd) - assert.DeepEqual(t, tc.expected, result) + assert.Equal(t, tc.expected, result) }) } } diff --git a/integration_tests/commands/async/command_list_test.go b/integration_tests/commands/async/command_list_test.go index ec9182cf7..56722db3c 100644 --- a/integration_tests/commands/async/command_list_test.go +++ b/integration_tests/commands/async/command_list_test.go @@ -5,7 +5,7 @@ import ( "net" "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandList(t *testing.T) { @@ -14,7 +14,7 @@ func TestCommandList(t *testing.T) { t.Run("Command list should not be empty", func(t *testing.T) { commandList := getCommandList(conn) - assert.Assert(t, len(commandList) > 0, + assert.True(t, len(commandList) > 0, fmt.Sprintf("Unexpected number of CLI commands found. expected greater than 0, %d found", len(commandList))) }) } diff --git a/integration_tests/commands/async/command_rename_test.go b/integration_tests/commands/async/command_rename_test.go index 6d3d0d499..4d5be2889 100644 --- a/integration_tests/commands/async/command_rename_test.go +++ b/integration_tests/commands/async/command_rename_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var renameKeysTestCases = []struct { diff --git a/integration_tests/commands/async/copy_test.go b/integration_tests/commands/async/copy_test.go index 4008cea1a..3fad1db47 100644 --- a/integration_tests/commands/async/copy_test.go +++ b/integration_tests/commands/async/copy_test.go @@ -4,8 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCopy(t *testing.T) { @@ -84,7 +83,7 @@ func TestCopy(t *testing.T) { // else compare the values as is. // This is to handle cases where the expected value is a json string with a different key order. if resOk && expOk && testutils.IsJSONResponse(resStr) && testutils.IsJSONResponse(expStr) { - testifyAssert.JSONEq(t, expStr, resStr) + assert.JSONEq(t, expStr, resStr) } else { assert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s", cmd) } diff --git a/integration_tests/commands/async/dbsize_test.go b/integration_tests/commands/async/dbsize_test.go index 901f5497a..7278127d8 100644 --- a/integration_tests/commands/async/dbsize_test.go +++ b/integration_tests/commands/async/dbsize_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDBSIZE(t *testing.T) { diff --git a/integration_tests/commands/async/del_test.go b/integration_tests/commands/async/del_test.go index d66c5334b..58305866f 100644 --- a/integration_tests/commands/async/del_test.go +++ b/integration_tests/commands/async/del_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDel(t *testing.T) { diff --git a/integration_tests/commands/async/deque_test.go b/integration_tests/commands/async/deque_test.go index 9119f682b..2e48bc04e 100644 --- a/integration_tests/commands/async/deque_test.go +++ b/integration_tests/commands/async/deque_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var deqRandGenerator *rand.Rand diff --git a/integration_tests/commands/async/discard_test.go b/integration_tests/commands/async/discard_test.go index 009aab70e..b26ef5c0a 100644 --- a/integration_tests/commands/async/discard_test.go +++ b/integration_tests/commands/async/discard_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDiscard(t *testing.T) { diff --git a/integration_tests/commands/async/dump_test.go b/integration_tests/commands/async/dump_test.go index 35adf1f4d..7ec050b91 100644 --- a/integration_tests/commands/async/dump_test.go +++ b/integration_tests/commands/async/dump_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDumpRestore(t *testing.T) { @@ -96,13 +96,13 @@ func TestDumpRestore(t *testing.T) { switch exp := expected.(type) { case string: - assert.DeepEqual(t, exp, result) + assert.Equal(t, exp, result) case []interface{}: - assert.Assert(t, testutils.UnorderedEqual(exp, result)) + assert.True(t, testutils.UnorderedEqual(exp, result)) case func(interface{}) bool: - assert.Assert(t, exp(result), cmd) + assert.True(t, exp(result), cmd) default: - assert.DeepEqual(t, expected, result) + assert.Equal(t, expected, result) } } }) diff --git a/integration_tests/commands/async/echo_test.go b/integration_tests/commands/async/echo_test.go index 1c46dadbd..06d14c022 100644 --- a/integration_tests/commands/async/echo_test.go +++ b/integration_tests/commands/async/echo_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestEcho(t *testing.T) { diff --git a/integration_tests/commands/async/exists_test.go b/integration_tests/commands/async/exists_test.go index 73d7cea8c..05d4da1f1 100644 --- a/integration_tests/commands/async/exists_test.go +++ b/integration_tests/commands/async/exists_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExists(t *testing.T) { diff --git a/integration_tests/commands/async/expire_test.go b/integration_tests/commands/async/expire_test.go index 519057662..129ef39ad 100644 --- a/integration_tests/commands/async/expire_test.go +++ b/integration_tests/commands/async/expire_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpire(t *testing.T) { @@ -232,10 +232,10 @@ func TestExpire(t *testing.T) { } if expected == "(nil)" { - assert.Assert(t, results[i] == "(nil)" || results[i] == "", + assert.True(t, results[i] == "(nil)" || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/async/expireat_test.go b/integration_tests/commands/async/expireat_test.go index b6c9210d3..4504e50fa 100644 --- a/integration_tests/commands/async/expireat_test.go +++ b/integration_tests/commands/async/expireat_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpireat(t *testing.T) { @@ -231,10 +231,10 @@ func TestExpireat(t *testing.T) { } if expected == "(nil)" { - assert.Assert(t, results[i] == "(nil)" || results[i] == "", + assert.True(t, results[i] == "(nil)" || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/async/expiretime_test.go b/integration_tests/commands/async/expiretime_test.go index 017216583..9ee7fb8ed 100644 --- a/integration_tests/commands/async/expiretime_test.go +++ b/integration_tests/commands/async/expiretime_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpiretime(t *testing.T) { @@ -86,10 +86,10 @@ func TestExpiretime(t *testing.T) { } if expected == "(nil)" { - assert.Assert(t, results[i] == "(nil)" || results[i] == "", + assert.True(t, results[i] == "(nil)" || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/async/flushdb_test.go b/integration_tests/commands/async/flushdb_test.go index 45b34dc8a..b11d9e804 100644 --- a/integration_tests/commands/async/flushdb_test.go +++ b/integration_tests/commands/async/flushdb_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestFLUSHDB(t *testing.T) { diff --git a/integration_tests/commands/async/get_test.go b/integration_tests/commands/async/get_test.go index f427a8f1d..697d4a03f 100644 --- a/integration_tests/commands/async/get_test.go +++ b/integration_tests/commands/async/get_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGet(t *testing.T) { diff --git a/integration_tests/commands/async/getdel_test.go b/integration_tests/commands/async/getdel_test.go index 262bffc6b..476631bed 100644 --- a/integration_tests/commands/async/getdel_test.go +++ b/integration_tests/commands/async/getdel_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetDel(t *testing.T) { diff --git a/integration_tests/commands/async/getex_test.go b/integration_tests/commands/async/getex_test.go index 8266cc671..8d552fac6 100644 --- a/integration_tests/commands/async/getex_test.go +++ b/integration_tests/commands/async/getex_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetEx(t *testing.T) { @@ -156,9 +156,9 @@ func TestGetEx(t *testing.T) { } result := FireCommand(conn, cmd) if tc.assertType[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else if tc.assertType[i] == "assert" { - assert.Assert(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/async/getset_test.go b/integration_tests/commands/async/getset_test.go index 0f4323232..ce43696e2 100644 --- a/integration_tests/commands/async/getset_test.go +++ b/integration_tests/commands/async/getset_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetSet(t *testing.T) { diff --git a/integration_tests/commands/async/hdel_test.go b/integration_tests/commands/async/hdel_test.go index 3f9fe5d0b..eb6bff455 100644 --- a/integration_tests/commands/async/hdel_test.go +++ b/integration_tests/commands/async/hdel_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHDEL(t *testing.T) { @@ -36,7 +36,7 @@ func TestHDEL(t *testing.T) { for _, tc := range testCases { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } } diff --git a/integration_tests/commands/async/hello_test.go b/integration_tests/commands/async/hello_test.go index 7b8620128..263865f35 100644 --- a/integration_tests/commands/async/hello_test.go +++ b/integration_tests/commands/async/hello_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHello(t *testing.T) { @@ -20,6 +20,6 @@ func TestHello(t *testing.T) { t.Run("HELLO command response", func(t *testing.T) { actual := FireCommand(conn, "HELLO") - assert.DeepEqual(t, expected, actual) + assert.Equal(t, expected, actual) }) } diff --git a/integration_tests/commands/async/hget_test.go b/integration_tests/commands/async/hget_test.go index d604ceff1..3b0618c91 100644 --- a/integration_tests/commands/async/hget_test.go +++ b/integration_tests/commands/async/hget_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHGET(t *testing.T) { @@ -43,7 +43,7 @@ func TestHGET(t *testing.T) { for _, tc := range testCases { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } } diff --git a/integration_tests/commands/async/hgetall_test.go b/integration_tests/commands/async/hgetall_test.go index 24b2e7b2f..72448f64b 100644 --- a/integration_tests/commands/async/hgetall_test.go +++ b/integration_tests/commands/async/hgetall_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHGETALL(t *testing.T) { @@ -52,7 +52,7 @@ func TestHGETALL(t *testing.T) { } } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/async/hmget_test.go b/integration_tests/commands/async/hmget_test.go index 72cb9804d..cad7e7894 100644 --- a/integration_tests/commands/async/hmget_test.go +++ b/integration_tests/commands/async/hmget_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHMGET(t *testing.T) { @@ -50,7 +50,7 @@ func TestHMGET(t *testing.T) { for i, cmd := range tc.commands { // Fire the command and get the result result := FireCommand(conn, cmd) - assert.DeepEqual(t, result, tc.expected[i]) + assert.Equal(t, result, tc.expected[i]) } }) } diff --git a/integration_tests/commands/async/hmset_test.go b/integration_tests/commands/async/hmset_test.go index 8cbd20e60..ce2508436 100644 --- a/integration_tests/commands/async/hmset_test.go +++ b/integration_tests/commands/async/hmset_test.go @@ -1,7 +1,7 @@ package async import ( - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" "testing" ) @@ -39,7 +39,7 @@ func TestHMSET(t *testing.T) { for _, tc := range testCases { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } } diff --git a/integration_tests/commands/async/hset_test.go b/integration_tests/commands/async/hset_test.go index 4e41c1898..81240c6d0 100644 --- a/integration_tests/commands/async/hset_test.go +++ b/integration_tests/commands/async/hset_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var ZERO int64 = 0 @@ -40,7 +40,7 @@ func TestHSET(t *testing.T) { for _, tc := range testCases { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } } diff --git a/integration_tests/commands/async/hsetnx_test.go b/integration_tests/commands/async/hsetnx_test.go index 1046e8ac7..dfb4b6e9d 100644 --- a/integration_tests/commands/async/hsetnx_test.go +++ b/integration_tests/commands/async/hsetnx_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHSETNX(t *testing.T) { @@ -32,7 +32,7 @@ func TestHSETNX(t *testing.T) { for _, tc := range testCases { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } } diff --git a/integration_tests/commands/async/json_arrpop_test.go b/integration_tests/commands/async/json_arrpop_test.go index 5993d0e62..cbfc7bf6b 100644 --- a/integration_tests/commands/async/json_arrpop_test.go +++ b/integration_tests/commands/async/json_arrpop_test.go @@ -4,8 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJSONARRPOP(t *testing.T) { @@ -49,14 +48,14 @@ func TestJSONARRPOP(t *testing.T) { jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, out.(string), jsonResult) + assert.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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } } }) diff --git a/integration_tests/commands/async/json_test.go b/integration_tests/commands/async/json_test.go index dc6957b35..9273daae3 100644 --- a/integration_tests/commands/async/json_test.go +++ b/integration_tests/commands/async/json_test.go @@ -8,8 +8,7 @@ import ( "github.com/bytedance/sonic" "github.com/dicedb/dice/testutils" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJSONOperations(t *testing.T) { @@ -189,7 +188,7 @@ func TestJSONOperations(t *testing.T) { if tc.getCmd != "" { result := FireCommand(conn, tc.getCmd) if testutils.IsJSONResponse(result.(string)) { - testifyAssert.JSONEq(t, tc.expected, result.(string)) + assert.JSONEq(t, tc.expected, result.(string)) } else { assert.Equal(t, tc.expected, result) } @@ -239,7 +238,7 @@ func TestJSONSetWithInvalidJSON(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, tc.command) - assert.Check(t, strings.HasPrefix(result.(string), tc.expected), fmt.Sprintf("Expected: %s, Got: %s", tc.expected, result)) + assert.True(t, strings.HasPrefix(result.(string), tc.expected), fmt.Sprintf("Expected: %s, Got: %s", tc.expected, result)) }) } } @@ -340,7 +339,7 @@ func TestJSONSetWithNXAndXX(t *testing.T) { result := FireCommand(conn, cmd) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -525,7 +524,7 @@ func TestJSONDelOperations(t *testing.T) { result := FireCommand(conn, cmd) stringResult, ok := result.(string) if ok && testutils.IsJSONResponse(stringResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), stringResult) + assert.JSONEq(t, tc.expected[i].(string), stringResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -620,7 +619,7 @@ func TestJSONForgetOperations(t *testing.T) { result := FireCommand(conn, cmd) stringResult, ok := result.(string) if ok && testutils.IsJSONResponse(stringResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), stringResult) + assert.JSONEq(t, tc.expected[i].(string), stringResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -735,7 +734,7 @@ func TestJsonStrlen(t *testing.T) { if ok { assert.Equal(t, tc.expected[i], stringResult) } else { - assert.Assert(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) } } }) @@ -795,7 +794,7 @@ func TestJSONMGET(t *testing.T) { assert.Equal(t, len(tc.expected), len(results)) for i := range results { if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), results[i].(string)) + assert.JSONEq(t, tc.expected[i].(string), results[i].(string)) } else { assert.Equal(t, tc.expected[i], results[i]) } @@ -813,7 +812,7 @@ func testJSONMGETRecursive(conn net.Conn) func(*testing.T) { return func(t *testing.T) { result := FireCommand(conn, "JSON.MGET doc1 doc2 $..a") results, ok := result.([]interface{}) - assert.Assert(t, ok, "Expected result to be a slice of interface{}") + assert.True(t, ok, "Expected result to be a slice of interface{}") assert.Equal(t, 2, len(results), "Expected 2 results") expectedSets := [][]int{ @@ -824,13 +823,13 @@ func testJSONMGETRecursive(conn net.Conn) func(*testing.T) { for i, res := range results { var actualSet []int err := sonic.UnmarshalString(res.(string), &actualSet) - assert.NilError(t, err, "Failed to unmarshal JSON") + assert.Nil(t, err, "Failed to unmarshal JSON") - assert.Assert(t, len(actualSet) == len(expectedSets[i]), + assert.True(t, len(actualSet) == len(expectedSets[i]), "Mismatch in number of elements for set %d", i) for _, expected := range expectedSets[i] { - assert.Assert(t, sliceContainsItem(actualSet, expected), + assert.True(t, sliceContainsItem(actualSet, expected), "Set %d does not contain expected value %d", i, expected) } } @@ -897,7 +896,7 @@ func TestJsonARRAPPEND(t *testing.T) { 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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } } }) @@ -975,7 +974,7 @@ func TestJsonNummultby(t *testing.T) { if tcase.assertType[i] == "equal" { assert.Equal(t, out, result) } else if tcase.assertType[i] == "deep_equal" { - assert.Assert(t, arraysArePermutations(deStringify(out.(string)), deStringify(result.(string)))) + assert.True(t, arraysArePermutations(deStringify(out.(string)), deStringify(result.(string)))) } } }) @@ -1095,7 +1094,8 @@ func TestJsonObjLen(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result := FireCommand(conn, cmd) - assert.DeepEqual(t, out, result) + + assert.Equal(t, out, result); } }) } @@ -1187,11 +1187,11 @@ func TestJSONNumIncrBy(t *testing.T) { case "equal": assert.Equal(t, out, result) case "perm_equal": - assert.Assert(t, arraysArePermutations(convertToArray(out.(string)), convertToArray(result.(string)))) + assert.True(t, arraysArePermutations(convertToArray(out.(string)), convertToArray(result.(string)))) case "range": - assert.Assert(t, result.(int64) <= tc.expected[i].(int64) && result.(int64) > 0, "Expected %v to be within 0 to %v", result, tc.expected[i]) + assert.True(t, result.(int64) <= tc.expected[i].(int64) && result.(int64) > 0, "Expected %v to be within 0 to %v", result, tc.expected[i]) case "json_equal": - testifyAssert.JSONEq(t, out.(string), result.(string)) + assert.JSONEq(t, out.(string), result.(string)) } } for i := 0; i < len(tc.cleanUp); i++ { @@ -1256,7 +1256,7 @@ func TestJsonSTRAPPEND(t *testing.T) { assert.Equal(t, "OK", result) result = FireCommand(conn, tc.getCmd) - assert.DeepEqual(t, tc.expected, result) + assert.ElementsMatch(t, tc.expected, result) }) } diff --git a/integration_tests/commands/async/jsonresp_test.go b/integration_tests/commands/async/jsonresp_test.go index 2a1ee58d4..82d72c216 100644 --- a/integration_tests/commands/async/jsonresp_test.go +++ b/integration_tests/commands/async/jsonresp_test.go @@ -3,8 +3,7 @@ package async import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJSONRESP(t *testing.T) { @@ -52,9 +51,9 @@ func TestJSONRESP(t *testing.T) { result := FireCommand(conn, cmd) if tcase.assert_type[i] == "equal" { - testifyAssert.Equal(t, out, result) + assert.Equal(t, out, result) } else if tcase.assert_type[i] == "deep_equal" { - assert.Assert(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } } }) diff --git a/integration_tests/commands/async/keys_test.go b/integration_tests/commands/async/keys_test.go index 654a4942b..9cd90f504 100644 --- a/integration_tests/commands/async/keys_test.go +++ b/integration_tests/commands/async/keys_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestKeys(t *testing.T) { @@ -54,9 +54,9 @@ func TestKeys(t *testing.T) { // because the order of keys is not guaranteed, we need to check if the result is an array if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } diff --git a/integration_tests/commands/async/mget_test.go b/integration_tests/commands/async/mget_test.go index f7a2dbdff..715c372ee 100644 --- a/integration_tests/commands/async/mget_test.go +++ b/integration_tests/commands/async/mget_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestMGET(t *testing.T) { @@ -49,9 +49,9 @@ func TestMGET(t *testing.T) { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/async/mset_test.go b/integration_tests/commands/async/mset_test.go index 1b1413b53..d0d8ba647 100644 --- a/integration_tests/commands/async/mset_test.go +++ b/integration_tests/commands/async/mset_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestMset(t *testing.T) { @@ -40,7 +40,7 @@ func TestMset(t *testing.T) { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -90,7 +90,7 @@ func TestMSETInconsistency(t *testing.T) { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/async/object_test.go b/integration_tests/commands/async/object_test.go index dd44dd032..93329411d 100644 --- a/integration_tests/commands/async/object_test.go +++ b/integration_tests/commands/async/object_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestObjectCommand(t *testing.T) { @@ -126,9 +126,9 @@ func TestObjectCommand(t *testing.T) { fmt.Println(cmd, result, tc.expected[i]) if tc.assertType[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else { - assert.Assert(t, result.(int64) >= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(int64) >= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } for _, cmd := range tc.cleanup { // run cleanup diff --git a/integration_tests/commands/async/qunwatch_test.go b/integration_tests/commands/async/qunwatch_test.go index b425b7f4f..536875d56 100644 --- a/integration_tests/commands/async/qunwatch_test.go +++ b/integration_tests/commands/async/qunwatch_test.go @@ -8,7 +8,7 @@ import ( "github.com/dicedb/dice/internal/clientio" "github.com/dicedb/dice/internal/sql" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) // need to test the following here: @@ -40,7 +40,7 @@ func TestQWatchUnwatch(t *testing.T) { // Check if the response is OK resp, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 3, len(resp.([]interface{}))) } @@ -51,7 +51,7 @@ func TestQWatchUnwatch(t *testing.T) { for _, sub := range subscribers[0:2] { rp := fireCommandAndGetRESPParser(sub, "Q.UNWATCH \""+qWatchQuery+"\"") resp, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, "OK", resp) } @@ -60,21 +60,21 @@ func TestQWatchUnwatch(t *testing.T) { // continue from the qwatch scenarios that ran previously FireCommand(publisher, "SET match:100:user:1 62") resp, err := respParsers[2].DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) expectedUpdate := []interface{}{[]interface{}{"match:100:user:5", int64(70)}, []interface{}{"match:100:user:1", int64(62)}, []interface{}{"match:100:user:0", int64(60)}} - assert.DeepEqual(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) + assert.Equal(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) FireCommand(publisher, "SET match:100:user:5 75") resp, err = respParsers[2].DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) expectedUpdate = []interface{}{[]interface{}{"match:100:user:5", int64(75)}, []interface{}{"match:100:user:1", int64(62)}, []interface{}{"match:100:user:0", int64(60)}} - assert.DeepEqual(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) + assert.Equal(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) FireCommand(publisher, "SET match:100:user:0 80") resp, err = respParsers[2].DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) expectedUpdate = []interface{}{[]interface{}{"match:100:user:0", int64(80)}, []interface{}{"match:100:user:5", int64(75)}, []interface{}{"match:100:user:1", int64(62)}} - assert.DeepEqual(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) + assert.Equal(t, []interface{}{sql.Qwatch, qWatchQuery, expectedUpdate}, resp) // Cleanup store for next tests for _, tc := range qWatchTestCases { diff --git a/integration_tests/commands/async/qwatch_test.go b/integration_tests/commands/async/qwatch_test.go index 119eb9019..87e08cfd3 100644 --- a/integration_tests/commands/async/qwatch_test.go +++ b/integration_tests/commands/async/qwatch_test.go @@ -11,7 +11,7 @@ import ( "github.com/dicedb/dice/internal/clientio" "github.com/dicedb/dice/internal/sql" dicedb "github.com/dicedb/dicedb-go" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) type qWatchTestCase struct { @@ -142,11 +142,11 @@ func subscribeToQWATCH(t *testing.T, subscribers []net.Conn, query string) []*cl respParsers := make([]*clientio.RESPParser, len(subscribers)) for i, subscriber := range subscribers { rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("Q.WATCH \"%s\"", query)) - assert.Assert(t, rp != nil) + assert.True(t, rp != nil) respParsers[i] = rp v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) castedValue, ok := v.([]interface{}) if !ok { t.Errorf("Type assertion to []interface{} failed for value: %v", v) @@ -164,9 +164,9 @@ func subscribeToQWATCHWithSDK(t *testing.T, subscribers []qWatchSDKSubscriber) [ for i, subscriber := range subscribers { qwatch := subscriber.client.QWatch(ctx) subscribers[i].qwatch = qwatch - assert.Assert(t, qwatch != nil) + assert.True(t, qwatch != nil) err := qwatch.WatchQuery(ctx, qWatchQuery) - assert.NilError(t, err) + assert.Nil(t, err) channels[i] = qwatch.Channel() <-channels[i] // Get the first message } @@ -188,7 +188,7 @@ func publishUpdate(t *testing.T, publisher interface{}, tc qWatchTestCase) { FireCommand(p, fmt.Sprintf("SET %s %d", key, tc.score)) case *dicedb.Client: err := p.Set(context.Background(), key, tc.score, 0).Err() - assert.NilError(t, err) + assert.Nil(t, err) } } @@ -206,13 +206,13 @@ func verifyUpdates(t *testing.T, receivers interface{}, expectedUpdates [][]inte func verifyRESPUpdates(t *testing.T, respParsers []*clientio.RESPParser, expectedUpdate []interface{}, query string) { for _, rp := range respParsers { v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) update, ok := v.([]interface{}) if !ok { t.Errorf("Type assertion to []interface{} failed for value: %v", v) return } - assert.DeepEqual(t, []interface{}{sql.Qwatch, query, expectedUpdate}, update) + assert.Equal(t, []interface{}{sql.Qwatch, query, expectedUpdate}, update) } } @@ -221,7 +221,7 @@ func verifySDKUpdates(t *testing.T, channels []<-chan *dicedb.QMessage, expected v := <-ch assert.Equal(t, len(v.Updates), len(expectedUpdate), v.Updates) for i, update := range v.Updates { - assert.DeepEqual(t, expectedUpdate[i], []interface{}{update.Key, update.Value}) + assert.Equal(t, expectedUpdate[i], []interface{}{update.Key, update.Value}) } } } @@ -339,11 +339,11 @@ func subscribeToJSONQueries(t *testing.T, subscribers []net.Conn, tests []JSONTe respParsers := make([]*clientio.RESPParser, len(subscribers)) for i, testCase := range tests { rp := fireCommandAndGetRESPParser(subscribers[i], fmt.Sprintf("Q.WATCH \"%s\"", testCase.qwatchQuery)) - assert.Assert(t, rp != nil) + assert.True(t, rp != nil) respParsers[i] = rp v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 3, len(v.([]interface{})), fmt.Sprintf("Expected 3 elements, got %v", v)) } return respParsers @@ -359,7 +359,7 @@ func runJSONScenarios(t *testing.T, publisher net.Conn, respParsers []*clientio. func verifyJSONUpdates(t *testing.T, rp *clientio.RESPParser, tc JSONTestCase) { for _, expectedUpdate := range tc.expectedUpdates { v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) response, ok := v.([]interface{}) if !ok { t.Errorf("Type assertion to []interface{} failed for value: %v", v) @@ -377,9 +377,9 @@ func verifyJSONUpdates(t *testing.T, rp *clientio.RESPParser, tc JSONTestCase) { assert.Equal(t, expectedUpdate[0].([]interface{})[0], update[0].([]interface{})[0], "Key mismatch") var expectedJSON, actualJSON interface{} - assert.NilError(t, sonic.UnmarshalString(tc.value, &expectedJSON)) - assert.NilError(t, sonic.UnmarshalString(update[0].([]interface{})[1].(string), &actualJSON)) - assert.DeepEqual(t, expectedJSON, actualJSON) + assert.Nil(t, sonic.UnmarshalString(tc.value, &expectedJSON)) + assert.Nil(t, sonic.UnmarshalString(update[0].([]interface{})[1].(string), &actualJSON)) + assert.Equal(t, expectedJSON, actualJSON) } } @@ -418,10 +418,10 @@ func setupJSONOrderByTest(t *testing.T) (net.Conn, net.Conn, func(), string) { func subscribeToJSONOrderByQuery(t *testing.T, subscriber net.Conn, watchquery string) *clientio.RESPParser { rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("Q.WATCH \"%s\"", watchquery)) - assert.Assert(t, rp != nil) + assert.True(t, rp != nil) v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 3, len(v.([]interface{})), fmt.Sprintf("Expected 3 elements, got %v", v)) return rp @@ -483,11 +483,11 @@ func verifyJSONOrderByUpdates(t *testing.T, rp *clientio.RESPParser, tc struct { // Decode the response v, err := rp.DecodeOne() - assert.NilError(t, err, "Failed to decode response") + assert.Nil(t, err, "Failed to decode response") // Cast the response to []interface{} response, ok := v.([]interface{}) - assert.Assert(t, ok, "Response is not of type []interface{}: %v", v) + assert.True(t, ok, "Response is not of type []interface{}: %v", v) // Verify response structure assert.Equal(t, 3, len(response), "Expected response to have 3 elements") @@ -495,7 +495,7 @@ func verifyJSONOrderByUpdates(t *testing.T, rp *clientio.RESPParser, tc struct { // Extract updates from the response updates, ok := response[2].([]interface{}) - assert.Assert(t, ok, "Updates are not of type []interface{}: %v", response[2]) + assert.True(t, ok, "Updates are not of type []interface{}: %v", response[2]) // Verify number of updates assert.Equal(t, len(expectedUpdates), len(updates), @@ -504,7 +504,7 @@ func verifyJSONOrderByUpdates(t *testing.T, rp *clientio.RESPParser, tc struct { // Verify each update for i, expectedRow := range expectedUpdates { actualRow, ok := updates[i].([]interface{}) - assert.Assert(t, ok, "Update row is not of type []interface{}: %v", updates[i]) + assert.True(t, ok, "Update row is not of type []interface{}: %v", updates[i]) // Verify key assert.Equal(t, expectedRow.([]interface{})[0], actualRow[0], @@ -513,9 +513,9 @@ func verifyJSONOrderByUpdates(t *testing.T, rp *clientio.RESPParser, tc struct { // Verify JSON value var actualJSON interface{} err := sonic.UnmarshalString(actualRow[1].(string), &actualJSON) - assert.NilError(t, err, "Failed to unmarshal JSON at index %d", i) + assert.Nil(t, err, "Failed to unmarshal JSON at index %d", i) - assert.DeepEqual(t, expectedRow.([]interface{})[1], actualJSON) + assert.Equal(t, expectedRow.([]interface{})[1], actualJSON) } } diff --git a/integration_tests/commands/async/select_test.go b/integration_tests/commands/async/select_test.go index ff7beb4ed..e016339f9 100644 --- a/integration_tests/commands/async/select_test.go +++ b/integration_tests/commands/async/select_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestSelect(t *testing.T) { @@ -12,11 +12,11 @@ func TestSelect(t *testing.T) { t.Run("SELECT command response", func(t *testing.T) { actual := FireCommand(conn, "SELECT 1") - assert.DeepEqual(t, "OK", actual) + assert.Equal(t, "OK", actual) }) t.Run("SELECT command error response", func(t *testing.T) { actual := FireCommand(conn, "SELECT") - assert.DeepEqual(t, "ERR wrong number of arguments for 'select' command", actual) + assert.Equal(t, "ERR wrong number of arguments for 'select' command", actual) }) } diff --git a/integration_tests/commands/async/set_data_cmd_test.go b/integration_tests/commands/async/set_data_cmd_test.go index d809f5973..ffac6fe07 100644 --- a/integration_tests/commands/async/set_data_cmd_test.go +++ b/integration_tests/commands/async/set_data_cmd_test.go @@ -5,12 +5,12 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func CustomDeepEqual(t *testing.T, a, b interface{}) { if a == nil || b == nil { - assert.DeepEqual(t, a, b) + assert.Equal(t, a, b) } switch a.(type) { @@ -23,7 +23,7 @@ func CustomDeepEqual(t *testing.T, a, b interface{}) { }) } - assert.DeepEqual(t, a, b) + assert.Equal(t, a, b) } func TestSetDataCommand(t *testing.T) { conn := getLocalConnection() @@ -231,7 +231,7 @@ func TestSetDataCommand(t *testing.T) { if tc.assertType[i] == "equal" { CustomDeepEqual(t, result, tc.expected[i]) } else if tc.assertType[i] == "assert" { - assert.Assert(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/async/toggle_test.go b/integration_tests/commands/async/toggle_test.go index 3e32663f6..c7846f211 100644 --- a/integration_tests/commands/async/toggle_test.go +++ b/integration_tests/commands/async/toggle_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func compareJSON(t *testing.T, expected, actual string) { @@ -16,10 +16,10 @@ func compareJSON(t *testing.T, expected, actual string) { err1 := json.Unmarshal([]byte(expected), &expectedMap) err2 := json.Unmarshal([]byte(actual), &actualMap) - assert.NilError(t, err1) - assert.NilError(t, err2) + assert.Nil(t, err1) + assert.Nil(t, err2) - assert.DeepEqual(t, expectedMap, actualMap) + assert.Equal(t, expectedMap, actualMap) } func TestJSONToggle(t *testing.T) { @@ -84,9 +84,9 @@ func TestJSONToggle(t *testing.T) { assert.Equal(t, expected, result) } case []interface{}: - assert.Assert(t, testutils.UnorderedEqual(expected, result)) + assert.True(t, testutils.UnorderedEqual(expected, result)) default: - assert.DeepEqual(t, expected, result) + assert.Equal(t, expected, result) } } }) diff --git a/integration_tests/commands/async/touch_test.go b/integration_tests/commands/async/touch_test.go index bf51f5088..5efe3f259 100644 --- a/integration_tests/commands/async/touch_test.go +++ b/integration_tests/commands/async/touch_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestTouch(t *testing.T) { @@ -52,9 +52,9 @@ func TestTouch(t *testing.T) { } result := FireCommand(conn, cmd) if tc.assertType[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else { - assert.Assert(t, result.(int64) >= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(int64) >= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/async/ttl_pttl_test.go b/integration_tests/commands/async/ttl_pttl_test.go index 8c5dd82d1..ad5db6e5c 100644 --- a/integration_tests/commands/async/ttl_pttl_test.go +++ b/integration_tests/commands/async/ttl_pttl_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestTTLPTTL(t *testing.T) { @@ -72,9 +72,9 @@ func TestTTLPTTL(t *testing.T) { } result := FireCommand(conn, cmd) if tc.assertType[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else if tc.assertType[i] == "assert" { - assert.Assert(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/async/type_test.go b/integration_tests/commands/async/type_test.go index 5f8bcbb00..4ba168cc9 100644 --- a/integration_tests/commands/async/type_test.go +++ b/integration_tests/commands/async/type_test.go @@ -3,7 +3,7 @@ package async import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestType(t *testing.T) { diff --git a/integration_tests/commands/http/append_test.go b/integration_tests/commands/http/append_test.go index fc2473055..8a1236be6 100644 --- a/integration_tests/commands/http/append_test.go +++ b/integration_tests/commands/http/append_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestAPPEND(t *testing.T) { @@ -96,7 +96,7 @@ func TestAPPEND(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - testifyAssert.Equal(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } exec.FireCommand(tc.cleanup[0]) }) diff --git a/integration_tests/commands/http/bit_test.go b/integration_tests/commands/http/bit_test.go index f318c9132..783ec4249 100644 --- a/integration_tests/commands/http/bit_test.go +++ b/integration_tests/commands/http/bit_test.go @@ -2,7 +2,7 @@ package http import ( "fmt" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" "testing" ) @@ -98,7 +98,7 @@ func TestBitPos(t *testing.T) { } result, _ := exec.FireCommand(tc.inCmd) - testifyAssert.Equal(t, tc.out, result, "Mismatch for cmd %s\n", tc.inCmd) + assert.Equal(t, tc.out, result, "Mismatch for cmd %s\n", tc.inCmd) }) } } diff --git a/integration_tests/commands/http/bloom_test.go b/integration_tests/commands/http/bloom_test.go index c29b782ea..f0cae9a12 100644 --- a/integration_tests/commands/http/bloom_test.go +++ b/integration_tests/commands/http/bloom_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - assert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestBloomFilter(t *testing.T) { diff --git a/integration_tests/commands/http/check_type_test.go b/integration_tests/commands/http/check_type_test.go index 7353b36cd..81127f284 100644 --- a/integration_tests/commands/http/check_type_test.go +++ b/integration_tests/commands/http/check_type_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) // This file may contain test cases for checking error messages across all commands diff --git a/integration_tests/commands/http/command_count_test.go b/integration_tests/commands/http/command_count_test.go index 265d3a7be..dd9e55338 100644 --- a/integration_tests/commands/http/command_count_test.go +++ b/integration_tests/commands/http/command_count_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandCount(t *testing.T) { @@ -39,9 +39,9 @@ func TestCommandCount(t *testing.T) { result, _ := exec.FireCommand(cmd) switch tc.assertType[c] { case "equal": - assert.DeepEqual(t, tc.expected[c], result) + assert.Equal(t, tc.expected[c], result) case "greater": - assert.Assert(t, result.(float64) >= tc.expected[c].(float64)) + assert.True(t, result.(float64) >= tc.expected[c].(float64)) } } diff --git a/integration_tests/commands/http/command_default_test.go b/integration_tests/commands/http/command_default_test.go index e3d2c8dfe..c4a67a8c7 100644 --- a/integration_tests/commands/http/command_default_test.go +++ b/integration_tests/commands/http/command_default_test.go @@ -5,19 +5,19 @@ import ( "testing" "github.com/dicedb/dice/internal/eval" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandDefault(t *testing.T) { exec := NewHTTPCommandExecutor() commands := getCommandDefault(exec) t.Run("Command should not be empty", func(t *testing.T) { - assert.Assert(t, len(commands) > 0, + assert.True(t, len(commands) > 0, fmt.Sprintf("Unexpected number of CLI commands found. expected greater than 0, %d found", len(commands))) }) t.Run("Command count matches", func(t *testing.T) { - assert.Assert(t, len(commands) == len(eval.DiceCmds), + assert.True(t, len(commands) == len(eval.DiceCmds), fmt.Sprintf("Unexpected number of CLI commands found. expected %d, %d found", len(eval.DiceCmds), len(commands))) }) } diff --git a/integration_tests/commands/http/command_getkeys_test.go b/integration_tests/commands/http/command_getkeys_test.go index f2cca2161..906117296 100644 --- a/integration_tests/commands/http/command_getkeys_test.go +++ b/integration_tests/commands/http/command_getkeys_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandGetKeys(t *testing.T) { @@ -93,7 +93,7 @@ func TestCommandGetKeys(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/command_help_test.go b/integration_tests/commands/http/command_help_test.go index 79ac368e3..d39af3044 100644 --- a/integration_tests/commands/http/command_help_test.go +++ b/integration_tests/commands/http/command_help_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandHelp(t *testing.T) { @@ -23,7 +23,7 @@ func TestCommandHelp(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for _, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } diff --git a/integration_tests/commands/http/command_info_test.go b/integration_tests/commands/http/command_info_test.go index a0f3726a8..034258007 100644 --- a/integration_tests/commands/http/command_info_test.go +++ b/integration_tests/commands/http/command_info_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandInfo(t *testing.T) { @@ -64,7 +64,7 @@ func TestCommandInfo(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/command_list_test.go b/integration_tests/commands/http/command_list_test.go index 8740b3332..87ed4508f 100644 --- a/integration_tests/commands/http/command_list_test.go +++ b/integration_tests/commands/http/command_list_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandList(t *testing.T) { @@ -23,10 +23,8 @@ func TestCommandList(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for _, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) - + assert.Equal(t, tc.expected[i], result) } - }) } } diff --git a/integration_tests/commands/http/command_rename_test.go b/integration_tests/commands/http/command_rename_test.go index 0bc9f3ddb..caef9ed8c 100644 --- a/integration_tests/commands/http/command_rename_test.go +++ b/integration_tests/commands/http/command_rename_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCommandRename(t *testing.T) { @@ -62,7 +62,7 @@ func TestCommandRename(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/copy_test.go b/integration_tests/commands/http/copy_test.go index 7e6d90850..51fdaa41a 100644 --- a/integration_tests/commands/http/copy_test.go +++ b/integration_tests/commands/http/copy_test.go @@ -5,7 +5,7 @@ import ( "github.com/dicedb/dice/testutils" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestCopy(t *testing.T) { @@ -110,20 +110,20 @@ func TestCopy(t *testing.T) { result, _ := exec.FireCommand(cmd) if result == nil { - testifyAssert.Equal(t, tc.expected[i], result, "Expected result to be nil for command %v", cmd) + assert.Equal(t, tc.expected[i], result, "Expected result to be nil for command %v", cmd) continue } if floatResult, ok := result.(float64); ok { - testifyAssert.Equal(t, tc.expected[i], floatResult, "Mismatch for command %v", cmd) + assert.Equal(t, tc.expected[i], floatResult, "Mismatch for command %v", cmd) continue } if resultStr, ok := result.(string); ok { if testutils.IsJSONResponse(resultStr) { - testifyAssert.JSONEq(t, tc.expected[i].(string), resultStr, "Mismatch in JSON response for command %v", cmd) + assert.JSONEq(t, tc.expected[i].(string), resultStr, "Mismatch in JSON response for command %v", cmd) } else { - testifyAssert.Equal(t, tc.expected[i], resultStr, "Mismatch for command %v", cmd) + assert.Equal(t, tc.expected[i], resultStr, "Mismatch for command %v", cmd) } } else { t.Fatalf("command %v returned unexpected type: %T", cmd, result) diff --git a/integration_tests/commands/http/countminsketch_test.go b/integration_tests/commands/http/countminsketch_test.go index 5f6ff09d4..bc051a147 100644 --- a/integration_tests/commands/http/countminsketch_test.go +++ b/integration_tests/commands/http/countminsketch_test.go @@ -4,7 +4,7 @@ import ( "log" "testing" - assert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestCMSInitByDim(t *testing.T) { diff --git a/integration_tests/commands/http/dbsize_test.go b/integration_tests/commands/http/dbsize_test.go index e5393a621..a76929d66 100644 --- a/integration_tests/commands/http/dbsize_test.go +++ b/integration_tests/commands/http/dbsize_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDBSize(t *testing.T) { @@ -66,7 +66,7 @@ func TestDBSize(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/decr_test.go b/integration_tests/commands/http/decr_test.go index 585d91d96..a41fc53d3 100644 --- a/integration_tests/commands/http/decr_test.go +++ b/integration_tests/commands/http/decr_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDECR(t *testing.T) { diff --git a/integration_tests/commands/http/del_test.go b/integration_tests/commands/http/del_test.go index f9732fb05..c7f8d693c 100644 --- a/integration_tests/commands/http/del_test.go +++ b/integration_tests/commands/http/del_test.go @@ -1,7 +1,7 @@ package http import ( - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" "testing" ) @@ -53,7 +53,7 @@ func TestDel(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/deque_test.go b/integration_tests/commands/http/deque_test.go index 23a577d15..234d551f0 100644 --- a/integration_tests/commands/http/deque_test.go +++ b/integration_tests/commands/http/deque_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var deqRandGenerator *rand.Rand @@ -103,7 +103,7 @@ func TestLPush(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -146,7 +146,7 @@ func TestRPush(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -224,7 +224,7 @@ func TestLPushLPop(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -303,7 +303,7 @@ func TestLPushRPop(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -382,7 +382,7 @@ func TestRPushLPop(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -461,7 +461,7 @@ func TestRPushRPop(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -502,7 +502,7 @@ func TestLRPushLRPop(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -549,7 +549,7 @@ func TestLLEN(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/echo_test.go b/integration_tests/commands/http/echo_test.go index 17af745b6..d2f8db08c 100644 --- a/integration_tests/commands/http/echo_test.go +++ b/integration_tests/commands/http/echo_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestEchoHttp(t *testing.T) { @@ -36,7 +36,7 @@ func TestEchoHttp(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/exists_test.go b/integration_tests/commands/http/exists_test.go index 52b1ab893..a7da3bd66 100644 --- a/integration_tests/commands/http/exists_test.go +++ b/integration_tests/commands/http/exists_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExistsHttp(t *testing.T) { @@ -86,7 +86,7 @@ func TestExistsHttp(t *testing.T) { if err != nil { log.Printf("Error executing command: %v", err) } - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/expire_test.go b/integration_tests/commands/http/expire_test.go index 4ba1d04e3..f785e89f8 100644 --- a/integration_tests/commands/http/expire_test.go +++ b/integration_tests/commands/http/expire_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpireHttp(t *testing.T) { @@ -224,10 +224,10 @@ func TestExpireHttp(t *testing.T) { t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) } if expected == nil { - assert.Assert(t, results[i] == nil || results[i] == "", + assert.True(t, results[i] == nil || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/http/expireat_test.go b/integration_tests/commands/http/expireat_test.go index f33f8d6f2..371c233d4 100644 --- a/integration_tests/commands/http/expireat_test.go +++ b/integration_tests/commands/http/expireat_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpireAtHttp(t *testing.T) { @@ -230,10 +230,10 @@ func TestExpireAtHttp(t *testing.T) { } if expected == nil { - assert.Assert(t, results[i] == nil || results[i] == "", + assert.True(t, results[i] == nil || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/http/expiretime_test.go b/integration_tests/commands/http/expiretime_test.go index df5cdad79..f3d4f0491 100644 --- a/integration_tests/commands/http/expiretime_test.go +++ b/integration_tests/commands/http/expiretime_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpireTimeHttp(t *testing.T) { @@ -96,10 +96,10 @@ func TestExpireTimeHttp(t *testing.T) { } if expected == nil { - assert.Assert(t, results[i] == nil || results[i] == "", + assert.True(t, results[i] == nil || results[i] == "", "Expected nil or empty result, got %v", results[i]) } else { - assert.DeepEqual(t, expected, results[i]) + assert.Equal(t, expected, results[i]) } } }) diff --git a/integration_tests/commands/http/get_test.go b/integration_tests/commands/http/get_test.go index 962920bd4..392ea42c6 100644 --- a/integration_tests/commands/http/get_test.go +++ b/integration_tests/commands/http/get_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGet(t *testing.T) { diff --git a/integration_tests/commands/http/getdel_test.go b/integration_tests/commands/http/getdel_test.go index 28a9454a2..9233a0b90 100644 --- a/integration_tests/commands/http/getdel_test.go +++ b/integration_tests/commands/http/getdel_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetDel(t *testing.T) { diff --git a/integration_tests/commands/http/getex_test.go b/integration_tests/commands/http/getex_test.go index 81f02c3d9..12fb9d03b 100644 --- a/integration_tests/commands/http/getex_test.go +++ b/integration_tests/commands/http/getex_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetEx(t *testing.T) { @@ -259,11 +259,11 @@ func TestGetEx(t *testing.T) { time.Sleep(tc.delay[i]) } result, err := exec.FireCommand(cmd) - assert.NilError(t, err) + assert.Nil(t, err) if tc.assertType[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else if tc.assertType[i] == "assert" { - assert.Assert(t, result.(float64) <= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(float64) <= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/http/getrange_test.go b/integration_tests/commands/http/getrange_test.go index 82f1c32a1..5de0e3444 100644 --- a/integration_tests/commands/http/getrange_test.go +++ b/integration_tests/commands/http/getrange_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestGETRANGE(t *testing.T) { @@ -75,7 +75,7 @@ func TestGETRANGE(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - testifyAssert.Equal(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } exec.FireCommand(tc.cleanup[0]) }) diff --git a/integration_tests/commands/http/getset_test.go b/integration_tests/commands/http/getset_test.go index b8bd21582..0072eee92 100644 --- a/integration_tests/commands/http/getset_test.go +++ b/integration_tests/commands/http/getset_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetSet(t *testing.T) { diff --git a/integration_tests/commands/http/hincrby_test.go b/integration_tests/commands/http/hincrby_test.go index 75c153c72..c01590762 100644 --- a/integration_tests/commands/http/hincrby_test.go +++ b/integration_tests/commands/http/hincrby_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHINCRBY(t *testing.T) { diff --git a/integration_tests/commands/http/hincrbyfloat_test.go b/integration_tests/commands/http/hincrbyfloat_test.go index 5cfb655e9..94b42e8e9 100644 --- a/integration_tests/commands/http/hincrbyfloat_test.go +++ b/integration_tests/commands/http/hincrbyfloat_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHINCRBYFLOAT(t *testing.T) { diff --git a/integration_tests/commands/http/hlen_test.go b/integration_tests/commands/http/hlen_test.go index 0658ff096..91daef6f4 100644 --- a/integration_tests/commands/http/hlen_test.go +++ b/integration_tests/commands/http/hlen_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHLen(t *testing.T) { @@ -79,9 +79,9 @@ func TestHLen(t *testing.T) { if err != nil { // Check if the error message matches the expected result log.Println(tc.expected[i]) - testifyAssert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) + assert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) } else { - testifyAssert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/hrandfield_test.go b/integration_tests/commands/http/hrandfield_test.go index b47d6447f..2067e9fe8 100644 --- a/integration_tests/commands/http/hrandfield_test.go +++ b/integration_tests/commands/http/hrandfield_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/google/go-cmp/cmp/cmpopts" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHRANDFIELD(t *testing.T) { @@ -86,7 +86,7 @@ func TestHRANDFIELD(t *testing.T) { if str, ok := result.(string); ok { assert.Equal(t, str, expected, "Unexpected result for command: %s", cmd) } else { - assert.DeepEqual(t, result, expected, cmpopts.EquateEmpty()) + assert.Equal(t, result, expected, cmpopts.EquateEmpty()) } } } @@ -126,7 +126,7 @@ func assertRandomFieldResult(t *testing.T, result interface{}, expected []string } // assert that all results are in the expected set or that there is a single valid result - assert.Assert(t, count == len(resultsList) || count == 1, + assert.True(t, count == len(resultsList) || count == 1, "Expected all results to be in the expected set or a single valid result. Got %d out of %d", count, len(resultsList)) } diff --git a/integration_tests/commands/http/hscan_test.go b/integration_tests/commands/http/hscan_test.go index 2af2f2398..d3c048210 100644 --- a/integration_tests/commands/http/hscan_test.go +++ b/integration_tests/commands/http/hscan_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHScan(t *testing.T) { @@ -139,9 +139,9 @@ func TestHScan(t *testing.T) { if err != nil { // Check if the error message matches the expected result log.Println(tc.expected[i]) - testifyAssert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) + assert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) } else { - testifyAssert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/hsetnx_test.go b/integration_tests/commands/http/hsetnx_test.go index f4925f26e..133ab883c 100644 --- a/integration_tests/commands/http/hsetnx_test.go +++ b/integration_tests/commands/http/hsetnx_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHSetNX(t *testing.T) { diff --git a/integration_tests/commands/http/hstrlen_test.go b/integration_tests/commands/http/hstrlen_test.go index f91d53b0b..7e4685463 100644 --- a/integration_tests/commands/http/hstrlen_test.go +++ b/integration_tests/commands/http/hstrlen_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHStrLen(t *testing.T) { diff --git a/integration_tests/commands/http/hyperloglog_test.go b/integration_tests/commands/http/hyperloglog_test.go index c2b9c4e40..70ac1e807 100644 --- a/integration_tests/commands/http/hyperloglog_test.go +++ b/integration_tests/commands/http/hyperloglog_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHyperLogLogCommands(t *testing.T) { diff --git a/integration_tests/commands/http/incr_by_float_test.go b/integration_tests/commands/http/incr_by_float_test.go index 0ad0054a6..d38c2fe0a 100644 --- a/integration_tests/commands/http/incr_by_float_test.go +++ b/integration_tests/commands/http/incr_by_float_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestINCRBYFLOAT(t *testing.T) { diff --git a/integration_tests/commands/http/incr_test.go b/integration_tests/commands/http/incr_test.go index 23da18c21..fa2aeaf3a 100644 --- a/integration_tests/commands/http/incr_test.go +++ b/integration_tests/commands/http/incr_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestINCR(t *testing.T) { diff --git a/integration_tests/commands/http/json_arrpop_test.go b/integration_tests/commands/http/json_arrpop_test.go index 4ea6cfe2e..4ee78384e 100644 --- a/integration_tests/commands/http/json_arrpop_test.go +++ b/integration_tests/commands/http/json_arrpop_test.go @@ -4,8 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJSONARRPOP(t *testing.T) { @@ -169,14 +168,14 @@ func TestJSONARRPOP(t *testing.T) { jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) continue } if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/json_test.go b/integration_tests/commands/http/json_test.go index 983a66ac2..9a8e8f4ae 100644 --- a/integration_tests/commands/http/json_test.go +++ b/integration_tests/commands/http/json_test.go @@ -9,9 +9,7 @@ import ( "github.com/bytedance/sonic" "github.com/dicedb/dice/testutils" "github.com/google/go-cmp/cmp/cmpopts" - testifyAssert "github.com/stretchr/testify/assert" - - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJSONOperations(t *testing.T) { @@ -194,7 +192,7 @@ func TestJSONOperations(t *testing.T) { result, _ := exec.FireCommand(cmd) if jsonResult, ok := result.(string); ok && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -212,9 +210,9 @@ func TestJSONOperations(t *testing.T) { if jsonResult, ok := result.(string); ok && testutils.IsJSONResponse(jsonResult) { var jsonPayload []interface{} json.Unmarshal([]byte(jsonResult), &jsonPayload) - assert.Assert(t, testutils.UnorderedEqual(tc.expected[i], jsonPayload)) + assert.True(t, testutils.UnorderedEqual(tc.expected[i], jsonPayload)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -253,7 +251,7 @@ func TestJSONSetWithInvalidCases(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.Check(t, strings.HasPrefix(result.(string), tc.expected[i].(string)), fmt.Sprintf("Expected: %s, Got: %s", tc.expected[i], result)) + assert.True(t, strings.HasPrefix(result.(string), tc.expected[i].(string)), fmt.Sprintf("Expected: %s, Got: %s", tc.expected[i], result)) } }) } @@ -321,7 +319,7 @@ func TestJSONSetWithNXAndXX(t *testing.T) { result, _ := exec.FireCommand(cmd) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -511,7 +509,7 @@ func TestJSONDelOperations(t *testing.T) { result, _ := exec.FireCommand(cmd) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -616,7 +614,7 @@ func TestJSONForgetOperations(t *testing.T) { result, _ := exec.FireCommand(cmd) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, tc.expected[i].(string), jsonResult) + assert.JSONEq(t, tc.expected[i].(string), jsonResult) } else { assert.Equal(t, tc.expected[i], result) } @@ -697,7 +695,7 @@ func TestJsonStrlen(t *testing.T) { if stringResult, ok := result.(string); ok { assert.Equal(t, tc.expected[i], stringResult) } else { - assert.Assert(t, testutils.UnorderedEqual(tc.expected[i], result.([]interface{}))) + assert.True(t, testutils.UnorderedEqual(tc.expected[i], result.([]interface{}))) } } }) @@ -789,7 +787,7 @@ func TestJSONMGET(t *testing.T) { resultStr, resultIsString := resultVal.(string) if isString && resultIsString && testutils.IsJSONResponse(expectedStr) { - testifyAssert.JSONEq(t, expectedStr, resultStr) + assert.JSONEq(t, expectedStr, resultStr) } else { assert.Equal(t, expectedVal, resultVal) } @@ -805,7 +803,7 @@ func TestJSONMGET(t *testing.T) { t.Run("MGET with recursive path", func(t *testing.T) { result, _ := exec.FireCommand(HTTPCommand{Command: "JSON.MGET", Body: map[string]interface{}{"keys": []interface{}{"doc1", "doc2"}, "path": "$..a"}}) results, ok := result.([]interface{}) - assert.Assert(t, ok, "Expected result to be a slice of interface{}") + assert.True(t, ok, "Expected result to be a slice of interface{}") expectedResults := [][]int{{1, 3}, {4, 6}} assert.Equal(t, len(expectedResults), len(results), "Expected 2 results") @@ -881,11 +879,11 @@ func TestJsonARRAPPEND(t *testing.T) { // because the order of keys is not guaranteed, we need to check if the result is an array if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), result.(string)) + assert.JSONEq(t, tc.expected[i].(string), result.(string)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -968,11 +966,11 @@ func TestJsonNummultby(t *testing.T) { if slice, ok := tc.expected[i].([]interface{}); ok { var resultPayload []interface{} sonic.UnmarshalString(result.(string), &resultPayload) - assert.Assert(t, testutils.UnorderedEqual(slice, resultPayload)) + assert.True(t, testutils.UnorderedEqual(slice, resultPayload)) } else if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), result.(string)) + assert.JSONEq(t, tc.expected[i].(string), result.(string)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -1126,9 +1124,9 @@ func TestJsonObjLen(t *testing.T) { result, _ := exec.FireCommand(cmd) if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -1214,11 +1212,11 @@ func TestJSONNumIncrBy(t *testing.T) { if slice, ok := tc.expected[i].([]interface{}); ok { var resultPayload []interface{} sonic.UnmarshalString(result.(string), &resultPayload) - assert.Assert(t, testutils.UnorderedEqual(slice, resultPayload)) + assert.True(t, testutils.UnorderedEqual(slice, resultPayload)) } else if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), result.(string)) + assert.JSONEq(t, tc.expected[i].(string), result.(string)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -1301,11 +1299,11 @@ func TestJsonARRINSERT(t *testing.T) { // because the order of keys is not guaranteed, we need to check if the result is an array if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), result.(string)) + assert.JSONEq(t, tc.expected[i].(string), result.(string)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) @@ -1428,11 +1426,17 @@ func TestJsonObjKeys(t *testing.T) { result, _ := exec.FireCommand(cmd) if slice, ok := tc.expected[i].([]interface{}); ok { - assert.DeepEqual(t, slice, tc.expected[i], cmpopts.SortSlices(func(a, b interface{}) bool { + assert.Equal(t, slice, tc.expected[i], cmpopts.SortSlices(func(a, b interface{}) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) })) } else { - assert.DeepEqual(t, tc.expected[i], result) + if _, ok := result.([]interface{}); ok { + assert.ElementsMatch(t, tc.expected[i].([]interface{}), result.([]interface{})) + } else { + // handle the case where result is not a []interface{} + assert.Equal(t, tc.expected[i], result) + } + } } }) @@ -1531,11 +1535,11 @@ func TestJsonARRTRIM(t *testing.T) { result, _ := exec.FireCommand(cmd) if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else if testutils.IsJSONResponse(tc.expected[i].(string)) { - testifyAssert.JSONEq(t, tc.expected[i].(string), result.(string)) + assert.JSONEq(t, tc.expected[i].(string), result.(string)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/keys_test.go b/integration_tests/commands/http/keys_test.go index 603ad8397..75c9b05b1 100644 --- a/integration_tests/commands/http/keys_test.go +++ b/integration_tests/commands/http/keys_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestKeys(t *testing.T) { @@ -74,9 +74,9 @@ func TestKeys(t *testing.T) { // because the order of keys is not guaranteed, we need to check if the result is an array if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/mget_test.go b/integration_tests/commands/http/mget_test.go index 21aa58100..93c97e0a7 100644 --- a/integration_tests/commands/http/mget_test.go +++ b/integration_tests/commands/http/mget_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestMGET(t *testing.T) { @@ -50,9 +50,9 @@ func TestMGET(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) if slice, ok := tc.expected[i].([]interface{}); ok { - assert.Assert(t, testutils.UnorderedEqual(slice, result)) + assert.True(t, testutils.UnorderedEqual(slice, result)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/mset_test.go b/integration_tests/commands/http/mset_test.go index dec941a2f..8b8182e1f 100644 --- a/integration_tests/commands/http/mset_test.go +++ b/integration_tests/commands/http/mset_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestMSET(t *testing.T) { @@ -46,7 +46,7 @@ func TestMSET(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/http/object_test.go b/integration_tests/commands/http/object_test.go index 749f9cbf9..e16cdab27 100644 --- a/integration_tests/commands/http/object_test.go +++ b/integration_tests/commands/http/object_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestOBJECT(t *testing.T) { @@ -45,9 +45,9 @@ func TestOBJECT(t *testing.T) { } result, _ := exec.FireCommand(cmd) if tc.assert_type[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else if tc.assert_type[i] == "assert" { - assert.Assert(t, result.(float64) >= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(float64) >= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/http/set_data_cmd_test.go b/integration_tests/commands/http/set_data_cmd_test.go index d354aa0db..e850a7569 100644 --- a/integration_tests/commands/http/set_data_cmd_test.go +++ b/integration_tests/commands/http/set_data_cmd_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestSetDataCmd(t *testing.T) { @@ -299,9 +299,9 @@ func TestSetDataCmd(t *testing.T) { result, _ := exec.FireCommand(cmd) switch tc.assert_type[i] { case "array": - testifyAssert.ElementsMatch(t, tc.expected[i], result) + assert.ElementsMatch(t, tc.expected[i], result) default: - testifyAssert.Equal(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/set_test.go b/integration_tests/commands/http/set_test.go index 5e5d0d13d..f4b3f3781 100644 --- a/integration_tests/commands/http/set_test.go +++ b/integration_tests/commands/http/set_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) type TestCase struct { @@ -55,7 +55,7 @@ func TestSet(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -330,9 +330,9 @@ func TestSetWithExat(t *testing.T) { for i, cmd := range tc.commands { result, _ := exec.FireCommand(cmd) if cmd.Command == "TTL" { - assert.Assert(t, result.(float64) <= tc.expected[i].(float64)) + assert.True(t, result.(float64) <= tc.expected[i].(float64)) } else { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/toggle_test.go b/integration_tests/commands/http/toggle_test.go index 7337e197d..4fd6529ec 100644 --- a/integration_tests/commands/http/toggle_test.go +++ b/integration_tests/commands/http/toggle_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func compareJSON(t *testing.T, expected, actual string) { @@ -15,10 +15,10 @@ func compareJSON(t *testing.T, expected, actual string) { err1 := json.Unmarshal([]byte(expected), &expectedMap) err2 := json.Unmarshal([]byte(actual), &actualMap) - assert.NilError(t, err1) - assert.NilError(t, err2) + assert.Nil(t, err1) + assert.Nil(t, err2) - assert.DeepEqual(t, expectedMap, actualMap) + assert.Equal(t, expectedMap, actualMap) } func TestJSONTOGGLE(t *testing.T) { exec := NewHTTPCommandExecutor() @@ -93,9 +93,9 @@ func TestJSONTOGGLE(t *testing.T) { assert.Equal(t, expected, result) } case []interface{}: - assert.Assert(t, testutils.UnorderedEqual(expected, result)) + assert.True(t, testutils.UnorderedEqual(expected, result)) default: - assert.DeepEqual(t, expected, result) + assert.Equal(t, expected, result) } } }) diff --git a/integration_tests/commands/http/touch_test.go b/integration_tests/commands/http/touch_test.go index c389f80e6..88400e51c 100644 --- a/integration_tests/commands/http/touch_test.go +++ b/integration_tests/commands/http/touch_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestTouch(t *testing.T) { diff --git a/integration_tests/commands/http/ttl_pttl_test.go b/integration_tests/commands/http/ttl_pttl_test.go index 1fc710ce5..0d3f8ce37 100644 --- a/integration_tests/commands/http/ttl_pttl_test.go +++ b/integration_tests/commands/http/ttl_pttl_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestTTLPTTL(t *testing.T) { @@ -111,9 +111,9 @@ func TestTTLPTTL(t *testing.T) { } result, _ := exec.FireCommand(cmd) if tc.assert_type[i] == "equal" { - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } else if tc.assert_type[i] == "assert" { - assert.Assert(t, result.(float64) <= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.True(t, result.(float64) <= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/integration_tests/commands/http/type_test.go b/integration_tests/commands/http/type_test.go index 136dd0ccc..550d451e2 100644 --- a/integration_tests/commands/http/type_test.go +++ b/integration_tests/commands/http/type_test.go @@ -3,7 +3,7 @@ package http import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestType(t *testing.T) { diff --git a/integration_tests/commands/http/zcard_test.go b/integration_tests/commands/http/zcard_test.go index 71ba1c443..29c869d97 100644 --- a/integration_tests/commands/http/zcard_test.go +++ b/integration_tests/commands/http/zcard_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestZCARD(t *testing.T) { @@ -82,9 +82,9 @@ func TestZCARD(t *testing.T) { if err != nil { // Check if the error message matches the expected result log.Println(tc.expected[i]) - testifyAssert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) + assert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) } else { - testifyAssert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/http/zrem_test.go b/integration_tests/commands/http/zrem_test.go index 42718be51..733c5c793 100644 --- a/integration_tests/commands/http/zrem_test.go +++ b/integration_tests/commands/http/zrem_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestZREM(t *testing.T) { @@ -88,9 +88,9 @@ func TestZREM(t *testing.T) { if err != nil { // Check if the error message matches the expected result log.Println(tc.expected[i]) - testifyAssert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) + assert.Equal(t, tc.expected[i], err.Error(), "Error message mismatch for cmd %s", cmd) } else { - testifyAssert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result, "Value mismatch for cmd %s, expected %v, got %v", cmd, tc.expected[i], result) } } }) diff --git a/integration_tests/commands/resp/append_test.go b/integration_tests/commands/resp/append_test.go index 4f252a84a..fc70851cf 100644 --- a/integration_tests/commands/resp/append_test.go +++ b/integration_tests/commands/resp/append_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestAPPEND(t *testing.T) { @@ -65,7 +65,7 @@ func TestAPPEND(t *testing.T) { for i := 0; i < len(tc.commands); i++ { result := FireCommand(conn, tc.commands[i]) expected := tc.expected[i] - testifyAssert.Equal(t, expected, result) + assert.Equal(t, expected, result) } for _, cmd := range tc.cleanup { diff --git a/integration_tests/commands/resp/bloom_test.go b/integration_tests/commands/resp/bloom_test.go index c5c37f116..bda85c284 100644 --- a/integration_tests/commands/resp/bloom_test.go +++ b/integration_tests/commands/resp/bloom_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - assert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestBFReserveAddInfoExists(t *testing.T) { diff --git a/integration_tests/commands/resp/command_docs_test.go b/integration_tests/commands/resp/command_docs_test.go index 55ff02196..f566a3c26 100644 --- a/integration_tests/commands/resp/command_docs_test.go +++ b/integration_tests/commands/resp/command_docs_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var getDocsTestCases = []struct { @@ -86,7 +86,7 @@ func TestCommandDocs(t *testing.T) { for _, tc := range getDocsTestCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, "COMMAND DOCS "+tc.inCmd) - assert.DeepEqual(t, tc.expected, result) + assert.Equal(t, tc.expected, result) }) } } diff --git a/integration_tests/commands/resp/command_getkeys_test.go b/integration_tests/commands/resp/command_getkeys_test.go index d1fd162a1..607bba994 100644 --- a/integration_tests/commands/resp/command_getkeys_test.go +++ b/integration_tests/commands/resp/command_getkeys_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var getKeysTestCases = []struct { @@ -31,7 +31,7 @@ func TestCommandGetKeys(t *testing.T) { for _, tc := range getKeysTestCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, "COMMAND GETKEYS "+tc.inCmd) - assert.DeepEqual(t, tc.expected, result) + assert.Equal(t, tc.expected, result) }) } } diff --git a/integration_tests/commands/resp/command_info_test.go b/integration_tests/commands/resp/command_info_test.go index 545d81984..e9d71ff5a 100644 --- a/integration_tests/commands/resp/command_info_test.go +++ b/integration_tests/commands/resp/command_info_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var getInfoTestCases = []struct { @@ -32,7 +32,7 @@ func TestCommandInfo(t *testing.T) { for _, tc := range getInfoTestCases { t.Run(tc.name, func(t *testing.T) { result := FireCommand(conn, "COMMAND INFO "+tc.inCmd) - assert.DeepEqual(t, tc.expected, result) + assert.Equal(t, tc.expected, result) }) } } diff --git a/integration_tests/commands/resp/countminsketch_test.go b/integration_tests/commands/resp/countminsketch_test.go index 574905157..e1c66575c 100644 --- a/integration_tests/commands/resp/countminsketch_test.go +++ b/integration_tests/commands/resp/countminsketch_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestCMSInitByDim(t *testing.T) { @@ -154,7 +154,7 @@ func TestCMSInfo(t *testing.T) { FireCommand(conn, "DEL cms_key2") for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -219,7 +219,7 @@ func TestCMSIncrBy(t *testing.T) { FireCommand(conn, "DEL cms_key3") for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -261,7 +261,7 @@ func TestCMSQuery(t *testing.T) { FireCommand(conn, "DEL cms_key4") for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -408,7 +408,7 @@ func TestCMSMerge(t *testing.T) { FireCommand(conn, "DEL cms_key5 test test1") for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/resp/get_test.go b/integration_tests/commands/resp/get_test.go index bb826a499..a7dc5f118 100644 --- a/integration_tests/commands/resp/get_test.go +++ b/integration_tests/commands/resp/get_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGet(t *testing.T) { diff --git a/integration_tests/commands/resp/getrange_test.go b/integration_tests/commands/resp/getrange_test.go index db5700769..14f068b3e 100644 --- a/integration_tests/commands/resp/getrange_test.go +++ b/integration_tests/commands/resp/getrange_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestGETRANGE(t *testing.T) { @@ -62,7 +62,7 @@ func TestGETRANGE(t *testing.T) { for i := 0; i < len(tc.commands); i++ { result := FireCommand(conn, tc.commands[i]) expected := tc.expected[i] - testifyAssert.Equal(t, expected, result) + assert.Equal(t, expected, result) } for _, cmd := range tc.cleanup { diff --git a/integration_tests/commands/resp/getset_test.go b/integration_tests/commands/resp/getset_test.go index 09a1eff69..8846bae9e 100644 --- a/integration_tests/commands/resp/getset_test.go +++ b/integration_tests/commands/resp/getset_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestGetSet(t *testing.T) { diff --git a/integration_tests/commands/resp/getwatch_test.go b/integration_tests/commands/resp/getwatch_test.go index c474ace47..537644b8f 100644 --- a/integration_tests/commands/resp/getwatch_test.go +++ b/integration_tests/commands/resp/getwatch_test.go @@ -9,7 +9,7 @@ import ( "github.com/dicedb/dice/internal/clientio" "github.com/dicedb/dicedb-go" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) type WatchSubscriber struct { @@ -57,11 +57,11 @@ func TestGETWATCH(t *testing.T) { respParsers := make([]*clientio.RESPParser, len(subscribers)) for i, subscriber := range subscribers { rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("GET.WATCH %s", getWatchKey)) - assert.Assert(t, rp != nil) + assert.True(t, rp != nil) respParsers[i] = rp v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) castedValue, ok := v.([]interface{}) if !ok { t.Errorf("Type assertion to []interface{} failed for value: %v", v) @@ -76,7 +76,7 @@ func TestGETWATCH(t *testing.T) { for _, rp := range respParsers { v, err := rp.DecodeOne() - assert.NilError(t, err) + assert.Nil(t, err) castedValue, ok := v.([]interface{}) if !ok { t.Errorf("Type assertion to []interface{} failed for value: %v", v) @@ -99,9 +99,9 @@ func TestGETWATCHWithSDK(t *testing.T) { for i, subscriber := range subscribers { watch := subscriber.client.WatchConn(context.Background()) subscribers[i].watch = watch - assert.Assert(t, watch != nil) + assert.True(t, watch != nil) firstMsg, err := watch.Watch(context.Background(), "GET", getWatchKey) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, firstMsg.Command, "GET") assert.Equal(t, firstMsg.Fingerprint, "1768826704") channels[i] = watch.Channel() @@ -109,7 +109,7 @@ func TestGETWATCHWithSDK(t *testing.T) { for _, tc := range getWatchTestCases { err := publisher.Set(context.Background(), tc.key, tc.val, 0).Err() - assert.NilError(t, err) + assert.Nil(t, err) for _, channel := range channels { v := <-channel @@ -130,9 +130,9 @@ func TestGETWATCHWithSDK2(t *testing.T) { for i, subscriber := range subscribers { watch := subscriber.client.WatchConn(context.Background()) subscribers[i].watch = watch - assert.Assert(t, watch != nil) + assert.True(t, watch != nil) firstMsg, err := watch.GetWatch(context.Background(), getWatchKey) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, firstMsg.Command, "GET") assert.Equal(t, firstMsg.Fingerprint, "1768826704") channels[i] = watch.Channel() @@ -140,7 +140,7 @@ func TestGETWATCHWithSDK2(t *testing.T) { for _, tc := range getWatchTestCases { err := publisher.Set(context.Background(), tc.key, tc.val, 0).Err() - assert.NilError(t, err) + assert.Nil(t, err) for _, channel := range channels { v := <-channel diff --git a/integration_tests/commands/resp/hincrby_test.go b/integration_tests/commands/resp/hincrby_test.go index 4d02ec07e..01f694a3b 100644 --- a/integration_tests/commands/resp/hincrby_test.go +++ b/integration_tests/commands/resp/hincrby_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHINCRBY(t *testing.T) { diff --git a/integration_tests/commands/resp/hincrbyfloat_test.go b/integration_tests/commands/resp/hincrbyfloat_test.go index 7696106b5..df16faeb7 100644 --- a/integration_tests/commands/resp/hincrbyfloat_test.go +++ b/integration_tests/commands/resp/hincrbyfloat_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHINCRBYFLOAT(t *testing.T) { diff --git a/integration_tests/commands/resp/hlen_test.go b/integration_tests/commands/resp/hlen_test.go index e0a4b6e51..eae9b1be8 100644 --- a/integration_tests/commands/resp/hlen_test.go +++ b/integration_tests/commands/resp/hlen_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHLEN(t *testing.T) { @@ -83,7 +83,7 @@ func TestHLEN(t *testing.T) { time.Sleep(tc.delays[i]) } result := FireCommand(conn, cmd) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/resp/hrandfield_test.go b/integration_tests/commands/resp/hrandfield_test.go index 5ba4b847c..45f879a7d 100644 --- a/integration_tests/commands/resp/hrandfield_test.go +++ b/integration_tests/commands/resp/hrandfield_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/google/go-cmp/cmp/cmpopts" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHRANDFIELD(t *testing.T) { @@ -71,7 +71,7 @@ func TestHRANDFIELD(t *testing.T) { if str, ok := result.(string); ok { assert.Equal(t, str, expected, "Unexpected result for command: %s", cmd) } else { - assert.DeepEqual(t, result, expected, cmpopts.EquateEmpty()) + assert.Equal(t, result, expected, cmpopts.EquateEmpty()) } } } @@ -111,7 +111,7 @@ func assertRandomFieldResult(t *testing.T, result interface{}, expected []string } // assert that all results are in the expected set or that there is a single valid result - assert.Assert(t, count == len(resultsList) || count == 1, + assert.True(t, count == len(resultsList) || count == 1, "Expected all results to be in the expected set or a single valid result. Got %d out of %d", count, len(resultsList)) } diff --git a/integration_tests/commands/resp/hscan_test.go b/integration_tests/commands/resp/hscan_test.go index aa540174a..d019cb852 100644 --- a/integration_tests/commands/resp/hscan_test.go +++ b/integration_tests/commands/resp/hscan_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHSCAN(t *testing.T) { @@ -128,7 +128,7 @@ func TestHSCAN(t *testing.T) { time.Sleep(tc.delays[i]) } result := FireCommand(conn, cmd) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/resp/hstrlen_test.go b/integration_tests/commands/resp/hstrlen_test.go index aff2d49cf..28c51bf13 100644 --- a/integration_tests/commands/resp/hstrlen_test.go +++ b/integration_tests/commands/resp/hstrlen_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHSTRLEN(t *testing.T) { @@ -66,7 +66,7 @@ func TestHSTRLEN(t *testing.T) { time.Sleep(tc.delays[i]) } result := FireCommand(conn, cmd) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/resp/hyperloglog_test.go b/integration_tests/commands/resp/hyperloglog_test.go index 75382c5d5..d6e09a3c7 100644 --- a/integration_tests/commands/resp/hyperloglog_test.go +++ b/integration_tests/commands/resp/hyperloglog_test.go @@ -5,7 +5,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestHyperLogLogCommands(t *testing.T) { @@ -90,7 +90,7 @@ func TestHyperLogLogCommands(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } diff --git a/integration_tests/commands/resp/incr_by_float_test.go b/integration_tests/commands/resp/incr_by_float_test.go index 95dd4443f..e47bdf717 100644 --- a/integration_tests/commands/resp/incr_by_float_test.go +++ b/integration_tests/commands/resp/incr_by_float_test.go @@ -3,7 +3,7 @@ package resp import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestINCRBYFLOAT(t *testing.T) { diff --git a/integration_tests/commands/resp/incr_test.go b/integration_tests/commands/resp/incr_test.go index 298b42059..24077f925 100644 --- a/integration_tests/commands/resp/incr_test.go +++ b/integration_tests/commands/resp/incr_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/dicedb/dice/internal/server/utils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestINCR(t *testing.T) { diff --git a/integration_tests/commands/resp/json_test.go b/integration_tests/commands/resp/json_test.go index 7915ed6d1..765c9babf 100644 --- a/integration_tests/commands/resp/json_test.go +++ b/integration_tests/commands/resp/json_test.go @@ -2,12 +2,11 @@ package resp import ( "fmt" + "sort" "testing" "github.com/dicedb/dice/testutils" - "github.com/google/go-cmp/cmp/cmpopts" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestJsonStrlen(t *testing.T) { @@ -86,7 +85,7 @@ func TestJsonStrlen(t *testing.T) { if ok { assert.Equal(t, tc.expected[i], stringResult) } else { - assert.Assert(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) } } }) @@ -289,7 +288,7 @@ func TestJsonObjLen(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result := FireCommand(conn, cmd) - assert.DeepEqual(t, out, result) + assert.Equal(t, out, result) } }) } @@ -366,14 +365,14 @@ func TestJSONARRPOP(t *testing.T) { jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, out.(string), jsonResult) + assert.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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } } }) @@ -430,7 +429,7 @@ func TestJsonARRAPPEND(t *testing.T) { 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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } } }) @@ -502,9 +501,9 @@ func TestJsonARRINSERT(t *testing.T) { 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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } else if tcase.assertType[i] == "jsoneq" { - testifyAssert.JSONEq(t, out.(string), result.(string)) + assert.JSONEq(t, out.(string), result.(string)) } } }) @@ -616,16 +615,32 @@ func TestJsonObjKeys(t *testing.T) { _, isString := out.(string) if isString { outInterface := []interface{}{out} - assert.DeepEqual(t, outInterface, expected) + assert.Equal(t, outInterface, expected) } else { - assert.DeepEqual(t, out.([]interface{}), expected, - cmpopts.SortSlices(func(a, b interface{}) bool { - return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) - })) + assert.ElementsMatch(t, + sortNestedSlices(expected), + sortNestedSlices(out.([]interface{})), + "Mismatch in JSON object keys") } }) } +} +func sortNestedSlices(data []interface{}) []interface{} { + result := make([]interface{}, len(data)) + for i, item := range data { + if slice, ok := item.([]interface{}); ok { + sorted := make([]interface{}, len(slice)) + copy(sorted, slice) + sort.Slice(sorted, func(i, j int) bool { + return fmt.Sprintf("%v", sorted[i]) < fmt.Sprintf("%v", sorted[j]) + }) + result[i] = sorted + } else { + result[i] = item + } + } + return result } func TestJsonARRTRIM(t *testing.T) { @@ -702,9 +717,9 @@ func TestJsonARRTRIM(t *testing.T) { 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{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } else if tcase.assertType[i] == "jsoneq" { - testifyAssert.JSONEq(t, out.(string), result.(string)) + assert.JSONEq(t, out.(string), result.(string)) } } }) diff --git a/integration_tests/commands/resp/set_test.go b/integration_tests/commands/resp/set_test.go index 1c015ff5a..e5ddb036c 100644 --- a/integration_tests/commands/resp/set_test.go +++ b/integration_tests/commands/resp/set_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) type TestCase struct { @@ -43,7 +43,7 @@ func TestSet(t *testing.T) { for i, cmd := range tc.commands { result := FireCommand(conn, cmd) - assert.DeepEqual(t, tc.expected[i], result) + assert.Equal(t, tc.expected[i], result) } }) } @@ -148,9 +148,9 @@ func TestSetWithExat(t *testing.T) { FireCommand(conn, "DEL k") assert.Equal(t, "OK", FireCommand(conn, "SET k v EXAT "+Etime), "Value mismatch for cmd SET k v EXAT "+Etime) assert.Equal(t, "v", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k") - assert.Assert(t, FireCommand(conn, "TTL k").(int64) <= 5, "Value mismatch for cmd TTL k") + assert.True(t, FireCommand(conn, "TTL k").(int64) <= 5, "Value mismatch for cmd TTL k") time.Sleep(3 * time.Second) - assert.Assert(t, FireCommand(conn, "TTL k").(int64) <= 3, "Value mismatch for cmd TTL k") + assert.True(t, FireCommand(conn, "TTL k").(int64) <= 3, "Value mismatch for cmd TTL k") time.Sleep(3 * time.Second) assert.Equal(t, "(nil)", FireCommand(conn, "GET k"), "Value mismatch for cmd GET k") assert.Equal(t, int64(-2), FireCommand(conn, "TTL k"), "Value mismatch for cmd TTL k") diff --git a/integration_tests/commands/websocket/hlen_test.go b/integration_tests/commands/websocket/hlen_test.go index a55e2ba42..093a89dd6 100644 --- a/integration_tests/commands/websocket/hlen_test.go +++ b/integration_tests/commands/websocket/hlen_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHLEN(t *testing.T) { @@ -68,8 +68,8 @@ func TestHLEN(t *testing.T) { time.Sleep(tc.delays[i]) } result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Nil(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/websocket/hscan_test.go b/integration_tests/commands/websocket/hscan_test.go index 2f65ae805..d9f1f8ca2 100644 --- a/integration_tests/commands/websocket/hscan_test.go +++ b/integration_tests/commands/websocket/hscan_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHSCAN(t *testing.T) { @@ -110,8 +110,8 @@ func TestHSCAN(t *testing.T) { time.Sleep(tc.delays[i]) } result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Nil(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/websocket/hstrlen_test.go b/integration_tests/commands/websocket/hstrlen_test.go index 736dbfbcc..3215a0b9e 100644 --- a/integration_tests/commands/websocket/hstrlen_test.go +++ b/integration_tests/commands/websocket/hstrlen_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestHSTRLEN(t *testing.T) { @@ -62,8 +62,8 @@ func TestHSTRLEN(t *testing.T) { time.Sleep(tc.delays[i]) } result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Nil(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/websocket/json_test.go b/integration_tests/commands/websocket/json_test.go index 32f244ec1..b5399d93b 100644 --- a/integration_tests/commands/websocket/json_test.go +++ b/integration_tests/commands/websocket/json_test.go @@ -1,13 +1,12 @@ package websocket import ( - "fmt" - "github.com/dicedb/dice/testutils" + "sort" "testing" - "github.com/google/go-cmp/cmp/cmpopts" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/dicedb/dice/testutils" + + "github.com/stretchr/testify/assert" ) func TestJSONClearOperations(t *testing.T) { @@ -19,8 +18,8 @@ func TestJSONClearOperations(t *testing.T) { defer func() { resp, err := exec.FireCommandAndReadResponse(conn, "DEL user") - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, float64(1), resp) + assert.Nil(t, err) + assert.Equal(t, float64(1), resp) }() testCases := []struct { @@ -93,8 +92,8 @@ func TestJSONClearOperations(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expected[i], result) + assert.Nil(t, err) + assert.Equal(t, tc.expected[i], result) } }) } @@ -110,8 +109,8 @@ func TestJsonStrlen(t *testing.T) { defer func() { resp, err := exec.FireCommandAndReadResponse(conn, "DEL doc") - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, float64(1), resp) + assert.Nil(t, err) + assert.Equal(t, float64(1), resp) }() testCases := []struct { @@ -181,12 +180,12 @@ func TestJsonStrlen(t *testing.T) { t.Run(tc.name, func(t *testing.T) { for i, cmd := range tc.commands { result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err, "error: %v", err) + assert.Nil(t, err, "error: %v", err) stringResult, ok := result.(string) if ok { - testifyAssert.Equal(t, tc.expected[i], stringResult) + assert.Equal(t, tc.expected[i], stringResult) } else { - testifyAssert.True(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(tc.expected[i].([]interface{}), result.([]interface{}))) } } }) @@ -207,8 +206,8 @@ func TestJsonObjLen(t *testing.T) { defer func() { resp, err := exec.FireCommandAndReadResponse(conn, "DEL obj") - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, float64(1), resp) + assert.Nil(t, err) + assert.Equal(t, float64(1), resp) }() testCases := []struct { @@ -310,8 +309,8 @@ func TestJsonObjLen(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, out, result) + assert.Nil(t, err) + assert.Equal(t, out, result) } }) } @@ -328,9 +327,9 @@ func TestJsonARRTRIM(t *testing.T) { defer func() { resp1, err := exec.FireCommandAndReadResponse(conn, "DEL a") resp2, err := exec.FireCommandAndReadResponse(conn, "DEL b") - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, float64(1), resp1) - testifyAssert.Equal(t, float64(1), resp2) + assert.Nil(t, err) + assert.Equal(t, float64(1), resp1) + assert.Equal(t, float64(1), resp2) }() testCases := []struct { @@ -395,13 +394,13 @@ func TestJsonARRTRIM(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) + assert.Nil(t, err) if tcase.assertType[i] == "equal" { - testifyAssert.Equal(t, out, result) + assert.Equal(t, out, result) } else if tcase.assertType[i] == "deep_equal" { - assert.Assert(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } else if tcase.assertType[i] == "jsoneq" { - testifyAssert.JSONEq(t, out.(string), result.(string)) + assert.JSONEq(t, out.(string), result.(string)) } } }) @@ -419,9 +418,9 @@ func TestJsonARRINSERT(t *testing.T) { defer func() { resp1, err := exec.FireCommandAndReadResponse(conn, "DEL a") resp2, err := exec.FireCommandAndReadResponse(conn, "DEL b") - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, float64(1), resp1) - testifyAssert.Equal(t, float64(1), resp2) + assert.Nil(t, err) + assert.Equal(t, float64(1), resp1) + assert.Equal(t, float64(1), resp2) }() testCases := []struct { @@ -474,13 +473,13 @@ func TestJsonARRINSERT(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) + assert.Nil(t, err) if tcase.assertType[i] == "equal" { - testifyAssert.Equal(t, out, result) + assert.Equal(t, out, result) } else if tcase.assertType[i] == "deep_equal" { - assert.Assert(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(out.([]interface{}), result.([]interface{}))) } else if tcase.assertType[i] == "jsoneq" { - testifyAssert.JSONEq(t, out.(string), result.(string)) + assert.JSONEq(t, out.(string), result.(string)) } } }) @@ -584,20 +583,30 @@ func TestJsonObjKeyslmao(t *testing.T) { for _, tcase := range testCases { t.Run(tcase.name, func(t *testing.T) { _, err := exec.FireCommandAndReadResponse(conn, tcase.setCommand) - testifyAssert.Nil(t, err) + assert.Nil(t, err) expected := tcase.expected - out, err := exec.FireCommandAndReadResponse(conn, tcase.testCommand) + out, _ := exec.FireCommandAndReadResponse(conn, tcase.testCommand) + + sortNested := func(data []interface{}) { + for _, elem := range data { + if innerSlice, ok := elem.([]interface{}); ok { + sort.Slice(innerSlice, func(i, j int) bool { + return innerSlice[i].(string) < innerSlice[j].(string) + }) + } + } + } - _, isString := out.(string) - if isString { - outInterface := []interface{}{out} - assert.DeepEqual(t, outInterface, expected) - } else { - assert.DeepEqual(t, out.([]interface{}), expected, - cmpopts.SortSlices(func(a, b interface{}) bool { - return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) - })) + if expected != nil { + sortNested(expected) } + if outSlice, ok := out.([]interface{}); ok { + sortNested(outSlice) + assert.ElementsMatch(t, expected, outSlice) + } else { + outInterface := []interface{}{out} + assert.ElementsMatch(t, expected, outInterface) + } }) } } @@ -671,18 +680,18 @@ func TestJSONARRPOP(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) + assert.Nil(t, err) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, out.(string), jsonResult) + assert.JSONEq(t, out.(string), jsonResult) continue } if tcase.assertType[i] == "equal" { assert.Equal(t, out, result) } else if tcase.assertType[i] == "deep_equal" { - testifyAssert.True(t, arraysArePermutations(tcase.expected[i].([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(tcase.expected[i].([]interface{}), result.([]interface{}))) } } }) @@ -739,18 +748,18 @@ func TestJsonARRAPPEND(t *testing.T) { cmd := tcase.commands[i] out := tcase.expected[i] result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) + assert.Nil(t, err) jsonResult, isString := result.(string) if isString && testutils.IsJSONResponse(jsonResult) { - testifyAssert.JSONEq(t, out.(string), jsonResult) + assert.JSONEq(t, out.(string), jsonResult) continue } if tcase.assertType[i] == "equal" { assert.Equal(t, out, result) } else if tcase.assertType[i] == "deep_equal" { - testifyAssert.True(t, arraysArePermutations(tcase.expected[i].([]interface{}), result.([]interface{}))) + assert.True(t, arraysArePermutations(tcase.expected[i].([]interface{}), result.([]interface{}))) } } }) diff --git a/integration_tests/commands/websocket/writeretry_test.go b/integration_tests/commands/websocket/writeretry_test.go index b0b270fc8..00aba1e1e 100644 --- a/integration_tests/commands/websocket/writeretry_test.go +++ b/integration_tests/commands/websocket/writeretry_test.go @@ -94,11 +94,20 @@ func startWebSocketServer() { // Helper to create a WebSocket connection for testing func createWebSocketConn(t *testing.T) *websocket.Conn { once.Do(startWebSocketServer) + var conn *websocket.Conn + var err error u := url.URL{Scheme: "ws", Host: serverAddr, Path: "/ws"} - conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - t.Fatalf("Failed to connect to WebSocket server: %v", err) + + // Retry up to 5 times with a short delay + for i := 0; i < 5; i++ { + conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil) + if err == nil { + return conn + } + time.Sleep(200 * time.Millisecond) // Adjust delay as necessary } - return conn + + t.Fatalf("Failed to connect to WebSocket server: %v", err) + return nil } diff --git a/integration_tests/commands/websocket/zcard_test.go b/integration_tests/commands/websocket/zcard_test.go index bd28f952a..34561134b 100644 --- a/integration_tests/commands/websocket/zcard_test.go +++ b/integration_tests/commands/websocket/zcard_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestZCARD(t *testing.T) { @@ -63,8 +63,8 @@ func TestZCARD(t *testing.T) { time.Sleep(tc.delays[i]) } result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Nil(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/commands/websocket/zrem_test.go b/integration_tests/commands/websocket/zrem_test.go index 0b78f6ed8..ef8f9e3ae 100644 --- a/integration_tests/commands/websocket/zrem_test.go +++ b/integration_tests/commands/websocket/zrem_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestZREM(t *testing.T) { @@ -70,8 +70,8 @@ func TestZREM(t *testing.T) { time.Sleep(tc.delays[i]) } result, err := exec.FireCommandAndReadResponse(conn, cmd) - testifyAssert.Nil(t, err) - testifyAssert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + assert.Nil(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) } }) } diff --git a/integration_tests/server/max_conn_test.go b/integration_tests/server/max_conn_test.go index d8d667ccc..3c18a0a3f 100644 --- a/integration_tests/server/max_conn_test.go +++ b/integration_tests/server/max_conn_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" commands "github.com/dicedb/dice/integration_tests/commands/async" ) diff --git a/internal/clientio/io_test.go b/internal/clientio/io_test.go index 3d4db2e13..f99e641ba 100644 --- a/internal/clientio/io_test.go +++ b/internal/clientio/io_test.go @@ -9,7 +9,7 @@ import ( "github.com/dicedb/dice/config" "github.com/dicedb/dice/internal/server/utils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) // MockReadWriter to simulate different io behaviors diff --git a/internal/clientio/resp_test.go b/internal/clientio/resp_test.go index 2a58f5b3e..97cbacd3e 100644 --- a/internal/clientio/resp_test.go +++ b/internal/clientio/resp_test.go @@ -8,7 +8,7 @@ import ( "github.com/dicedb/dice/internal/clientio" "github.com/dicedb/dice/internal/server/utils" - testifyAssert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestSimpleStringDecode(t *testing.T) { @@ -194,7 +194,7 @@ func TestBoolean(t *testing.T) { for _, v := range tests { ev := clientio.Encode(v.input, false) - testifyAssert.Equal(t, ev, v.output) + assert.Equal(t, ev, v.output) } } @@ -215,6 +215,6 @@ func TestInteger(t *testing.T) { for _, v := range tests { ev := clientio.Encode(v.input, false) - testifyAssert.Equal(t, ev, v.output) + assert.Equal(t, ev, v.output) } } diff --git a/internal/dencoding/dencoding_benchmark_test.go b/internal/dencoding/dencoding_benchmark_test.go index 686a1da4d..66787c403 100644 --- a/internal/dencoding/dencoding_benchmark_test.go +++ b/internal/dencoding/dencoding_benchmark_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/dicedb/dice/internal/dencoding" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func BenchmarkEncodeDecodeInt(b *testing.B) { diff --git a/internal/dencoding/int_test.go b/internal/dencoding/int_test.go index a9bc93bf2..9d2663044 100644 --- a/internal/dencoding/int_test.go +++ b/internal/dencoding/int_test.go @@ -7,7 +7,7 @@ import ( "github.com/dicedb/dice/internal/dencoding" "github.com/dicedb/dice/testutils" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestDencodingUInt(t *testing.T) { @@ -51,7 +51,7 @@ func TestDencodingUInt(t *testing.T) { for value, expected := range testCases { t.Run(fmt.Sprintf("value_%d", value), func(t *testing.T) { encoded := dencoding.EncodeUInt(uint64(value)) - assert.Assert(t, testutils.EqualByteSlice(encoded, expected), "Unexpected encoding") + assert.True(t, testutils.EqualByteSlice(encoded, expected), "Unexpected encoding") }) } }) @@ -102,7 +102,7 @@ func TestDencodingInt(t *testing.T) { for value, expected := range testCases { t.Run(fmt.Sprintf("value_%d", value), func(t *testing.T) { encoded := dencoding.EncodeInt(int64(value)) - assert.Assert(t, testutils.EqualByteSlice(encoded, expected), "Unexpected encoding") + assert.True(t, testutils.EqualByteSlice(encoded, expected), "Unexpected encoding") }) } }) diff --git a/internal/eval/bloom_test.go b/internal/eval/bloom_test.go index 32452ec7d..8e027eaa4 100644 --- a/internal/eval/bloom_test.go +++ b/internal/eval/bloom_test.go @@ -9,7 +9,7 @@ import ( "github.com/dicedb/dice/internal/clientio" dstore "github.com/dicedb/dice/internal/store" - assert "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestBloomFilter(t *testing.T) { diff --git a/internal/eval/bytearray_test.go b/internal/eval/bytearray_test.go index 86b945a90..efaa10f06 100644 --- a/internal/eval/bytearray_test.go +++ b/internal/eval/bytearray_test.go @@ -3,7 +3,7 @@ package eval import ( "testing" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestMixedOperations(t *testing.T) { @@ -143,9 +143,9 @@ func TestDeepCopy(t *testing.T) { // Modify the deepCopy's data to ensure it's independent of the original deepCopy.data[0] = 9 - assert.Assert(t, deepCopy.data[0] != original.data[0], "ByteArray DeepCopy did not create an independent deepCopy, original and deepCopy data are linked") + assert.True(t, deepCopy.data[0] != original.data[0], "ByteArray DeepCopy did not create an independent deepCopy, original and deepCopy data are linked") // Modify the original's data to ensure it doesn't affect the deepCopy original.data[1] = 8 - assert.Assert(t, deepCopy.data[1] != original.data[1], "ByteArray DeepCopy did not create an independent deepCopy, original and deepCopy data are linked") + assert.True(t, deepCopy.data[1] != original.data[1], "ByteArray DeepCopy did not create an independent deepCopy, original and deepCopy data are linked") } diff --git a/internal/eval/bytelist_test.go b/internal/eval/bytelist_test.go index 822974eb9..2d22d2ebd 100644 --- a/internal/eval/bytelist_test.go +++ b/internal/eval/bytelist_test.go @@ -2,7 +2,7 @@ package eval import ( "bytes" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" "testing" ) @@ -95,9 +95,9 @@ func TestByteListDeepCopy(t *testing.T) { // Verify that changes to the deepCopy do not affect the original deepCopy.head.buf[0] = 9 - assert.Assert(t, original.head.buf[0] != deepCopy.head.buf[0], "Original and deepCopy head buffer should not be linked") + assert.True(t, original.head.buf[0] != deepCopy.head.buf[0], "Original and deepCopy head buffer should not be linked") // Verify that changes to the original do not affect the deepCopy original.head.buf[1] = 8 - assert.Assert(t, original.head.buf[1] != deepCopy.head.buf[1], "Original and deepCopy head buffer should not be linked") + assert.True(t, original.head.buf[1] != deepCopy.head.buf[1], "Original and deepCopy head buffer should not be linked") } diff --git a/internal/eval/deque_test.go b/internal/eval/deque_test.go index da2422908..b21ee3243 100644 --- a/internal/eval/deque_test.go +++ b/internal/eval/deque_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) var deqRandGenerator *rand.Rand diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 069c5d435..cf28589ca 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -23,8 +23,7 @@ import ( diceerrors "github.com/dicedb/dice/internal/errors" "github.com/dicedb/dice/internal/object" dstore "github.com/dicedb/dice/internal/store" - testifyAssert "github.com/stretchr/testify/assert" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) type evalTestCase struct { @@ -361,16 +360,16 @@ func testEvalSET(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -499,16 +498,16 @@ func testEvalGET(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -573,16 +572,16 @@ func testEvalGETSET(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -967,16 +966,16 @@ func testEvalJSONARRTRIM(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { - testifyAssert.Equal(t, tt.migratedOutput.Result, response.Result) + assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -1135,16 +1134,16 @@ func testEvalJSONARRINSERT(t *testing.T, store *dstore.Store) { if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { - testifyAssert.Equal(t, tt.migratedOutput.Result, response.Result) + assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -1249,16 +1248,16 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { if tt.migratedOutput.Result != nil { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { - assert.DeepEqual(t, slice, response.Result) + assert.Equal(t, slice, response.Result) } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -1433,16 +1432,16 @@ func testEvalJSONOBJLEN(t *testing.T, store *dstore.Store) { response := evalJSONOBJLEN(tt.input, store) if tt.migratedOutput.Result != nil { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { - assert.DeepEqual(t, slice, response.Result) + assert.Equal(t, slice, response.Result) } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -1749,16 +1748,16 @@ func testEvalJSONCLEAR(t *testing.T, store *dstore.Store) { response := evalJSONCLEAR(tt.input, store) if tt.migratedOutput.Result != nil { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { - assert.DeepEqual(t, slice, response.Result) + assert.Equal(t, slice, response.Result) } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -1962,8 +1961,8 @@ func testEvalJSONSET(t *testing.T, store *dstore.Store) { input: []string{"doc", "$", "{\"a\":}"}, output: nil, validator: func(output []byte) { - assert.Assert(t, output != nil) - assert.Assert(t, strings.Contains(string(output), "-ERR invalid JSON:")) + assert.True(t, output != nil) + assert.True(t, strings.Contains(string(output), "-ERR invalid JSON:")) }, }, "valid json path": { @@ -2222,9 +2221,9 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -2368,9 +2367,9 @@ func testEvalTTL(t *testing.T, store *dstore.Store) { }, input: []string{"EXISTING_KEY"}, validator: func(output []byte) { - assert.Assert(t, output != nil) - assert.Assert(t, !bytes.Equal(output, clientio.RespMinusOne)) - assert.Assert(t, !bytes.Equal(output, clientio.RespMinusTwo)) + assert.True(t, output != nil) + assert.True(t, !bytes.Equal(output, clientio.RespMinusOne)) + assert.True(t, !bytes.Equal(output, clientio.RespMinusTwo)) }, }, "key exists but expired": { @@ -2934,22 +2933,22 @@ func testEvalHVALS(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if responseBytes, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") } } else { fmt.Printf("G1: %v | %v\n", response.Result, tt.migratedOutput.Result) switch e := tt.migratedOutput.Result.(type) { case []interface{}, []string: - testifyAssert.ElementsMatch(t, e, response.Result) + assert.ElementsMatch(t, e, response.Result) default: assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -3087,7 +3086,7 @@ func testEvalHEXISTS(t *testing.T, store *dstore.Store) { // If has result if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { // fmt.Printf("%v | %v\n", responseBytes, expectedBytes) - testifyAssert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") } } else { // If has error @@ -3095,9 +3094,9 @@ func testEvalHEXISTS(t *testing.T, store *dstore.Store) { } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -3385,16 +3384,16 @@ func testEvalJSONSTRLEN(t *testing.T, store *dstore.Store) { response := evalJSONSTRLEN(tt.input, store) if tt.migratedOutput.Result != nil { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { - assert.DeepEqual(t, slice, response.Result) + assert.Equal(t, slice, response.Result) } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -3482,7 +3481,7 @@ func testEvalJSONNUMINCRBY(t *testing.T, store *dstore.Store) { endIndex := strings.Index(outPutString, "]") arrayString := outPutString[startIndex+1 : endIndex] arr := strings.Split(arrayString, ",") - testifyAssert.ElementsMatch(t, arr, []string{"25", "20", "7", "15", "null", "null"}) + assert.ElementsMatch(t, arr, []string{"25", "20", "7", "15", "null", "null"}) }, }, @@ -3527,7 +3526,7 @@ func testEvalJSONNUMINCRBY(t *testing.T, store *dstore.Store) { endIndex := strings.Index(outPutString, "]") arrayString := outPutString[startIndex+1 : endIndex] arr := strings.Split(arrayString, ",") - testifyAssert.ElementsMatch(t, arr, []string{"3", "4", "7", "null", "null"}) + assert.ElementsMatch(t, arr, []string{"3", "4", "7", "null", "null"}) }, }, @@ -3589,7 +3588,7 @@ func runMigratedEvalTests(t *testing.T, tests map[string]evalTestCase, evalFunc } if tc.migratedOutput.Error != nil { - testifyAssert.EqualError(t, output.Error, tc.migratedOutput.Error.Error()) + assert.EqualError(t, output.Error, tc.migratedOutput.Error.Error()) return } @@ -3597,17 +3596,17 @@ func runMigratedEvalTests(t *testing.T, tests map[string]evalTestCase, evalFunc // TODO: Make this generic so that all kind of slices can be handled if b, ok := output.Result.([]byte); ok && tc.migratedOutput.Result != nil { if expectedBytes, ok := tc.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else if a, ok := output.Result.([]string); ok && tc.migratedOutput.Result != nil { if expectedStringSlice, ok := tc.migratedOutput.Result.([]string); ok { - testifyAssert.ElementsMatch(t, a, expectedStringSlice) + assert.ElementsMatch(t, a, expectedStringSlice) } } else { - testifyAssert.Equal(t, tc.migratedOutput.Result, output.Result) + assert.Equal(t, tc.migratedOutput.Result, output.Result) } - testifyAssert.NoError(t, output.Error) + assert.NoError(t, output.Error) }) } } @@ -3696,8 +3695,8 @@ func testEvalHSET(t *testing.T, store *dstore.Store) { // Check if the map is saved correctly in the store res, err := getValueFromHashMap(key, field, store) - assert.Assert(t, err == nil) - assert.DeepEqual(t, res, clientio.Encode(mockValue, false)) + assert.Nil(t, err) + assert.Equal(t, res, clientio.Encode(mockValue, false)) }, input: []string{ "KEY_MOCK", @@ -3782,8 +3781,8 @@ func testEvalHMSET(t *testing.T, store *dstore.Store) { // Check if the map is saved correctly in the store res, err := getValueFromHashMap(key, field, store) - assert.Assert(t, err == nil) - assert.DeepEqual(t, res, clientio.Encode(mockValue, false)) + assert.True(t, err == nil) + assert.Equal(t, res, clientio.Encode(mockValue, false)) }, input: []string{ "KEY_MOCK", @@ -3857,13 +3856,13 @@ func testEvalHKEYS(t *testing.T, store *dstore.Store) { if responseBytes, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { // fmt.Printf("G: %v | %v\n", responseBytes, expectedBytes) - testifyAssert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(responseBytes, expectedBytes), "expected and actual byte slices should be equal") } } else { // fmt.Printf("G1: %v | %v\n", response.Result, tt.migratedOutput.Result) switch e := tt.migratedOutput.Result.(type) { case []interface{}, []string: - testifyAssert.ElementsMatch(t, e, response.Result) + assert.ElementsMatch(t, e, response.Result) default: assert.Equal(t, tt.migratedOutput.Result, response.Result) } @@ -3871,9 +3870,9 @@ func testEvalHKEYS(t *testing.T, store *dstore.Store) { if tt.migratedOutput.Error != nil { // fmt.Printf("E: %v | %v\n", response.Error, tt.migratedOutput.Error.Error()) - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -4464,7 +4463,7 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { obj := store.Get(key) expr, err := jp.ParseString(path) - assert.NilError(t, err, "error parsing path") + assert.Nil(t, err, "error parsing path") results := expr.Get(obj.Value) assert.Equal(t, len(results), 1) @@ -4491,16 +4490,16 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { if tt.migratedOutput.Result != nil { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { - assert.DeepEqual(t, slice, response.Result) + assert.Equal(t, slice, response.Result) } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -4878,16 +4877,16 @@ func testEvalJSONOBJKEYS(t *testing.T, store *dstore.Store) { if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { - testifyAssert.Equal(t, tt.migratedOutput.Result, response.Result) + assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -5412,8 +5411,8 @@ func testEvalSETEX(t *testing.T, store *dstore.Store) { // Check if the TTL is set correctly (should be 5 seconds or less) ttlValue := evalTTL([]string{"TEST_KEY"}, store) ttl, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(string(ttlValue)), ":")) - assert.NilError(t, err, "Failed to parse TTL") - assert.Assert(t, ttl > 0 && ttl <= 5) + assert.Nil(t, err, "Failed to parse TTL") + assert.True(t, ttl > 0 && ttl <= 5) // Wait for the key to expire mockTime.SetTime(mockTime.CurrTime.Add(6 * time.Second)) @@ -5438,8 +5437,8 @@ func testEvalSETEX(t *testing.T, store *dstore.Store) { // Check if the TTL is set correctly ttlValue := evalTTL([]string{"EXISTING_KEY"}, store) ttl, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(string(ttlValue)), ":")) - assert.NilError(t, err, "Failed to parse TTL") - assert.Assert(t, ttl > 0 && ttl <= 10) + assert.Nil(t, err, "Failed to parse TTL") + assert.True(t, ttl > 0 && ttl <= 10) }, }, } @@ -5458,16 +5457,16 @@ func testEvalSETEX(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } } }) @@ -5638,16 +5637,16 @@ func testEvalINCRBYFLOAT(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -5688,7 +5687,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(0, true), validator: func(output []byte) { expectedResult := []byte{} - assert.DeepEqual(t, expectedResult, store.Get("dest{t}").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("dest{t}").Value.(*ByteArray).data) }, }, "BITOP NOT (known string)": { @@ -5699,7 +5698,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(4, true), validator: func(output []byte) { expectedResult := []byte{0x55, 0xff, 0x00, 0xaa} - assert.DeepEqual(t, expectedResult, store.Get("dest{t}").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("dest{t}").Value.(*ByteArray).data) }, }, "BITOP where dest and target are the same key": { @@ -5710,7 +5709,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(4, true), validator: func(output []byte) { expectedResult := []byte{0x55, 0xff, 0x00, 0xaa} - assert.DeepEqual(t, expectedResult, store.Get("s").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("s").Value.(*ByteArray).data) }, }, "BITOP AND|OR|XOR don't change the string with single input key": { @@ -5721,7 +5720,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(3, true), validator: func(output []byte) { expectedResult := []byte{0x01, 0x02, 0xff} - assert.DeepEqual(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) }, }, "BITOP missing key is considered a stream of zero": { @@ -5732,7 +5731,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(3, true), validator: func(output []byte) { expectedResult := []byte{0x00, 0x00, 0x00} - assert.DeepEqual(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) }, }, "BITOP shorter keys are zero-padded to the key with max length": { @@ -5744,7 +5743,7 @@ func testEvalBITOP(t *testing.T, store *dstore.Store) { output: clientio.Encode(4, true), validator: func(output []byte) { expectedResult := []byte{0x01, 0x02, 0xff, 0x00} - assert.DeepEqual(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) + assert.Equal(t, expectedResult, store.Get("res1{t}").Value.(*ByteArray).data) }, }, "BITOP with non string source key": { @@ -5831,13 +5830,13 @@ func testEvalHRANDFIELD(t *testing.T, store *dstore.Store) { }, input: []string{"KEY_MOCK"}, newValidator: func(output interface{}) { - assert.Assert(t, output != nil) + assert.True(t, output != nil) stringSlice, ok := output.([]string) if !ok { - testifyAssert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) + assert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) } resultString := strings.Join(stringSlice, " ") - assert.Assert(t, + assert.True(t, resultString == "field1" || resultString == "field2", "Unexpected field returned: %s", resultString) }, @@ -5860,10 +5859,10 @@ func testEvalHRANDFIELD(t *testing.T, store *dstore.Store) { }, input: []string{"KEY_MOCK", "2"}, newValidator: func(output interface{}) { - assert.Assert(t, output != nil) + assert.True(t, output != nil) stringSlice, ok := output.([]string) if !ok { - testifyAssert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) + assert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) } decodedResult := strings.Join(stringSlice, " ") fields := []string{"field1", "field2", "field3"} @@ -5875,7 +5874,7 @@ func testEvalHRANDFIELD(t *testing.T, store *dstore.Store) { } } - assert.Assert(t, count == 2) + assert.True(t, count == 2) }, }, "key exists with count and WITHVALUES argument": { @@ -5896,10 +5895,10 @@ func testEvalHRANDFIELD(t *testing.T, store *dstore.Store) { }, input: []string{"KEY_MOCK", "2", WithValues}, newValidator: func(output interface{}) { - assert.Assert(t, output != nil) + assert.True(t, output != nil) stringSlice, ok := output.([]string) if !ok { - testifyAssert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) + assert.Error(t, diceerrors.ErrUnexpectedType("[]string", reflect.TypeOf(output))) } decodedResult := strings.Join(stringSlice, " ") fieldsAndValues := []string{"field1", "value1", "field2", "value2", "field3", "value3"} @@ -7628,16 +7627,16 @@ func testEvalINCR(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -7715,16 +7714,16 @@ func testEvalINCRBY(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -7802,16 +7801,16 @@ func testEvalDECR(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } @@ -7889,16 +7888,16 @@ func testEvalDECRBY(t *testing.T, store *dstore.Store) { // Handle comparison for byte slices if b, ok := response.Result.([]byte); ok && tt.migratedOutput.Result != nil { if expectedBytes, ok := tt.migratedOutput.Result.([]byte); ok { - testifyAssert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") + assert.True(t, bytes.Equal(b, expectedBytes), "expected and actual byte slices should be equal") } } else { assert.Equal(t, tt.migratedOutput.Result, response.Result) } if tt.migratedOutput.Error != nil { - testifyAssert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) + assert.EqualError(t, response.Error, tt.migratedOutput.Error.Error()) } else { - testifyAssert.NoError(t, response.Error) + assert.NoError(t, response.Error) } }) } diff --git a/internal/server/utils/jsontype_test.go b/internal/server/utils/jsontype_test.go index b2ecf2502..768568fe7 100644 --- a/internal/server/utils/jsontype_test.go +++ b/internal/server/utils/jsontype_test.go @@ -1,7 +1,7 @@ package utils import ( - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" "testing" ) diff --git a/internal/sql/dsql_test.go b/internal/sql/dsql_test.go index c44f15196..0976679c7 100644 --- a/internal/sql/dsql_test.go +++ b/internal/sql/dsql_test.go @@ -5,7 +5,7 @@ import ( "github.com/dicedb/dice/internal/server/utils" "github.com/xwb1989/sqlparser" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestParseQuery(t *testing.T) { @@ -225,16 +225,16 @@ func TestParseQuery(t *testing.T) { if tt.wantErr { assert.Error(t, err, tt.error) } else { - assert.NilError(t, err) - assert.DeepEqual(t, tt.want.Selection, got.Selection) - assert.DeepEqual(t, tt.want.OrderBy, got.OrderBy) + assert.Nil(t, err) + assert.Equal(t, tt.want.Selection, got.Selection) + assert.Equal(t, tt.want.OrderBy, got.OrderBy) assert.Equal(t, tt.want.Limit, got.Limit) //if tt.want.Where == nil { // assert.Assert(t, got.Where == nil) //} else { - assert.Assert(t, got.Where != nil) - assert.DeepEqual(t, tt.want.Where, got.Where) + assert.True(t, got.Where != nil) + assert.Equal(t, tt.want.Where, got.Where) //} } }) @@ -279,17 +279,17 @@ func TestParseSelectExpressions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { stmt, err := sqlparser.Parse(replaceCustomSyntax(tt.sql)) - assert.NilError(t, err) + assert.Nil(t, err) selectStmt, ok := stmt.(*sqlparser.Select) - assert.Assert(t, ok) + assert.True(t, ok) got, err := parseSelectExpressions(selectStmt) if tt.wantErr { - assert.Assert(t, err != nil) + assert.True(t, err != nil) } else { - assert.NilError(t, err) - assert.DeepEqual(t, tt.want, got) + assert.Nil(t, err) + assert.Equal(t, tt.want, got) } }) } @@ -367,17 +367,17 @@ func TestParseOrderBy(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stmt, err := sqlparser.Parse(replaceCustomSyntax(tt.sql)) - assert.NilError(t, err) + assert.Nil(t, err) selectStmt, ok := stmt.(*sqlparser.Select) - assert.Assert(t, ok) + assert.True(t, ok) got, err := parseOrderBy(selectStmt) if tt.wantErr { - assert.Assert(t, err != nil) + assert.True(t, err != nil) } else { - assert.NilError(t, err) - assert.DeepEqual(t, tt.want, got) + assert.Nil(t, err) + assert.Equal(t, tt.want, got) } }) } @@ -410,16 +410,16 @@ func TestParseLimit(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stmt, err := sqlparser.Parse(replaceCustomSyntax(tt.sql)) - assert.NilError(t, err) + assert.Nil(t, err) selectStmt, ok := stmt.(*sqlparser.Select) - assert.Assert(t, ok) + assert.True(t, ok) got, err := parseLimit(selectStmt) if tt.wantErr { - assert.Assert(t, err != nil) + assert.True(t, err != nil) } else { - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, tt.want, got) } }) diff --git a/internal/sql/executor_test.go b/internal/sql/executor_test.go index 7feb9f4d3..918c61f95 100644 --- a/internal/sql/executor_test.go +++ b/internal/sql/executor_test.go @@ -1,17 +1,17 @@ package sql_test import ( - "github.com/dicedb/dice/internal/object" - "github.com/dicedb/dice/internal/sql" "sort" "testing" + "github.com/dicedb/dice/internal/object" + "github.com/dicedb/dice/internal/sql" + "github.com/bytedance/sonic" "github.com/dicedb/dice/internal/server/utils" dstore "github.com/dicedb/dice/internal/store" + "github.com/stretchr/testify/assert" "github.com/xwb1989/sqlparser" - "gotest.tools/v3/assert" - "gotest.tools/v3/assert/cmp" ) type keyValue struct { @@ -47,11 +47,11 @@ func TestExecuteQueryOrderBykey(t *testing.T) { queryString := "SELECT $key, $value WHERE $key like 'k*' ORDER BY $key ASC" query, err := sql.ParseQuery(queryString) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), len(simpleKVDataset)) sortedDataset := make([]keyValue, len(simpleKVDataset)) @@ -64,7 +64,7 @@ func TestExecuteQueryOrderBykey(t *testing.T) { for i, data := range sortedDataset { assert.Equal(t, result[i].Key, data.key) - assert.DeepEqual(t, result[i].Value.Value, data.value) + assert.Equal(t, result[i].Value.Value, data.value) } } @@ -74,11 +74,11 @@ func TestExecuteQueryBasicOrderByValue(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'k*' ORDER BY $value ASC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), len(simpleKVDataset)) sortedDataset := make([]keyValue, len(simpleKVDataset)) @@ -91,7 +91,7 @@ func TestExecuteQueryBasicOrderByValue(t *testing.T) { for i, data := range sortedDataset { assert.Equal(t, result[i].Key, data.key) - assert.DeepEqual(t, result[i].Value.Value, data.value) + assert.Equal(t, result[i].Value.Value, data.value) } } @@ -101,12 +101,12 @@ func TestExecuteQueryLimit(t *testing.T) { queryStr := "SELECT $value WHERE $key like 'k*' ORDER BY $key ASC LIMIT 3" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) - assert.Assert(t, cmp.Len(result, 3)) // Checks if limit is respected + assert.Nil(t, err) + assert.Equal(t, len(result), 3) // Checks if limit is respected sortedDataset := make([]keyValue, len(simpleKVDataset)) copy(sortedDataset, simpleKVDataset) @@ -118,7 +118,7 @@ func TestExecuteQueryLimit(t *testing.T) { for i, data := range sortedDataset[:3] { assert.Equal(t, result[i].Key, utils.EmptyStr) - assert.DeepEqual(t, result[i].Value.Value, data.value) + assert.Equal(t, result[i].Value.Value, data.value) } } @@ -128,12 +128,12 @@ func TestExecuteQueryNoMatch(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'x*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) - assert.Assert(t, cmp.Len(result, 0)) // No keys match "x*" + assert.Nil(t, err) + assert.Equal(t, len(result), 0) // No keys match "x*" } func TestExecuteQueryWithWhere(t *testing.T) { @@ -142,50 +142,50 @@ func TestExecuteQueryWithWhere(t *testing.T) { t.Run("BasicWhereClause", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $value = 'v3' AND $key like 'k*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for WHERE clause") assert.Equal(t, result[0].Key, "k3") - assert.DeepEqual(t, result[0].Value.Value, "v3") + assert.Equal(t, result[0].Value.Value, "v3") }) t.Run("EmptyResult", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $value = 'nonexistent' AND $key like 'k*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 0, "Expected empty result for non-matching WHERE clause") }) t.Run("ComplexWhereClause", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $value > 'v2' AND $value < 'v5' AND $key like 'k*' ORDER BY $key ASC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 2, "Expected 2 results for complex WHERE clause") - assert.DeepEqual(t, []string{result[0].Key, result[1].Key}, []string{"k2", "k3"}) + assert.Equal(t, []string{result[0].Key, result[1].Key}, []string{"k2", "k3"}) }) t.Run("ComparingKeyWithValue", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key = $value" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for comparison between key and value") assert.Equal(t, result[0].Key, "k") - assert.DeepEqual(t, result[0].Value.Value, "k") + assert.Equal(t, result[0].Value.Value, "k") }) } @@ -196,7 +196,7 @@ func TestExecuteQueryWithIncompatibleTypes(t *testing.T) { t.Run("ComparingStrWithInt", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $value = 42 AND $key like 'k*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) _, err = sql.ExecuteQuery(&query, store.GetStore()) @@ -223,7 +223,7 @@ func TestExecuteQueryWithEdgeCases(t *testing.T) { result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 0, "Expected 0 results due to case sensitivity") }) @@ -233,15 +233,15 @@ func TestExecuteQueryWithEdgeCases(t *testing.T) { result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 2, "Expected 2 results for WHERE clause on key") - assert.DeepEqual(t, []string{result[0].Key, result[1].Key}, []string{"k4", "k5"}) + assert.Equal(t, []string{result[0].Key, result[1].Key}, []string{"k4", "k5"}) }) t.Run("UnsupportedOperator", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $value regexp '%3' AND $key like 'k*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) _, err = sql.ExecuteQuery(&query, store.GetStore()) @@ -251,11 +251,11 @@ func TestExecuteQueryWithEdgeCases(t *testing.T) { t.Run("EmptyKeyRegex", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like ''" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 0, "Expected no keys to be returned for empty regex") }) } @@ -291,97 +291,97 @@ func TestExecuteQueryWithJsonExpressionInWhere(t *testing.T) { t.Run("BasicWhereClauseWithJSON", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.name' = 'Tom' AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 results for WHERE clause") assert.Equal(t, result[0].Key, "json1") var expected, actual interface{} - assert.NilError(t, sonic.UnmarshalString(`{"name":"Tom"}`, &expected)) - assert.NilError(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) - assert.DeepEqual(t, actual, expected) + assert.Nil(t, sonic.UnmarshalString(`{"name":"Tom"}`, &expected)) + assert.Nil(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) + assert.Equal(t, actual, expected) }) t.Run("EmptyResult", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.name' = 'Bill' AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 0, "Expected empty result for non-matching WHERE clause") }) t.Run("WhereClauseWithFloats", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.score' > 13.15 AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for WHERE clause with floating point values") assert.Equal(t, result[0].Key, "json2") var expected, actual interface{} - assert.NilError(t, sonic.UnmarshalString(`{"name":"Bob","score":18.1}`, &expected)) - assert.NilError(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) - assert.DeepEqual(t, actual, expected) + assert.Nil(t, sonic.UnmarshalString(`{"name":"Bob","score":18.1}`, &expected)) + assert.Nil(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) + assert.Equal(t, actual, expected) }) t.Run("WhereClauseWithInteger", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.scoreInt' > 13 AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for WHERE clause with integer values") assert.Equal(t, result[0].Key, "json3") var expected, actual interface{} - assert.NilError(t, sonic.UnmarshalString(`{"scoreInt":20}`, &expected)) - assert.NilError(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) - assert.DeepEqual(t, actual, expected) + assert.Nil(t, sonic.UnmarshalString(`{"scoreInt":20}`, &expected)) + assert.Nil(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) + assert.Equal(t, actual, expected) }) t.Run("NestedWhereClause", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.field1.field2.field3.score' < 13 AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for WHERE clause with nested json") assert.Equal(t, result[0].Key, "json4") var expected, actual interface{} - assert.NilError(t, sonic.UnmarshalString(`{"field1":{"field2":{"field3":{"score":2}}}}`, &expected)) - assert.NilError(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) - assert.DeepEqual(t, actual, expected) + assert.Nil(t, sonic.UnmarshalString(`{"field1":{"field2":{"field3":{"score":2}}}}`, &expected)) + assert.Nil(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) + assert.Equal(t, actual, expected) }) t.Run("ComplexWhereClause", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE '$value.field1.field2.field3.score' > '$value.field1.score2' AND $key like 'json*'" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), 1, "Expected 1 result for Complex WHERE clause expression") assert.Equal(t, result[0].Key, "json5") var expected, actual interface{} - assert.NilError(t, sonic.UnmarshalString(`{"field1":{"field2":{"field3":{"score":18}},"score2":5}}`, &expected)) - assert.NilError(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) - assert.DeepEqual(t, actual, expected) + assert.Nil(t, sonic.UnmarshalString(`{"field1":{"field2":{"field3":{"score":18}},"score2":5}}`, &expected)) + assert.Nil(t, sonic.UnmarshalString(result[0].Value.Value.(string), &actual)) + assert.Equal(t, actual, expected) }) } @@ -400,11 +400,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderBySimpleJSONField", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' ORDER BY $value.name ASC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 5, len(result), "Expected 5 results") assert.Equal(t, "json3", result[0].Key) // Alice @@ -426,11 +426,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderByNumericJSONField", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' ORDER BY $value.age DESC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 5, len(result)) assert.Equal(t, "json5", result[0].Key) // Charlie, age 50 @@ -452,11 +452,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderByNestedJSONField", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' ORDER BY '$value.nested.field.value' ASC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 5, len(result)) assert.Equal(t, "json3", result[0].Key) // Alice, nested.field.value: 15 validateJSONStringRepresentationsAreEqual(t, jsonOrderDataset[0].value, result[0].Value.Value.(string)) @@ -477,11 +477,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderByMixedTypes", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' ORDER BY $value.score DESC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) // No ordering guarantees for mixed types. assert.Equal(t, 5, len(result)) }) @@ -489,11 +489,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderByWithWhereClause", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' AND '$value.age' > 30 ORDER BY $value.name DESC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, 3, len(result), "Expected 3 results (age > 30, ordered by name)") assert.Equal(t, "json4", result[0].Key) // Eve, age 32 validateJSONStringRepresentationsAreEqual(t, jsonOrderDataset[4].value, result[0].Value.Value.(string)) @@ -508,11 +508,11 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { t.Run("OrderByNonExistentField", func(t *testing.T) { queryStr := "SELECT $key, $value WHERE $key like 'json*' ORDER BY $value.nonexistent ASC" query, err := sql.ParseQuery(queryStr) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) // No ordering guarantees for non-existent field references. assert.Equal(t, 5, len(result), "Expected 5 results") }) @@ -522,9 +522,9 @@ func TestExecuteQueryWithJsonOrderBy(t *testing.T) { func validateJSONStringRepresentationsAreEqual(t *testing.T, expectedJSONString, actualJSONString string) { t.Helper() var expectedValue, actualValue interface{} - assert.NilError(t, sonic.UnmarshalString(expectedJSONString, &expectedValue)) - assert.NilError(t, sonic.UnmarshalString(actualJSONString, &actualValue)) - assert.DeepEqual(t, actualValue, expectedValue) + assert.Nil(t, sonic.UnmarshalString(expectedJSONString, &expectedValue)) + assert.Nil(t, sonic.UnmarshalString(actualJSONString, &actualValue)) + assert.Equal(t, actualValue, expectedValue) } // Dataset will be used for LIKE comparisons @@ -625,10 +625,10 @@ func TestExecuteQueryWithLikeStringComparisons(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { query, err := sql.ParseQuery(tc.query) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), tc.expectLen, "Expected %d results, got %d", tc.expectLen, len(result)) resultKeys := make([]string, len(result)) @@ -636,7 +636,7 @@ func TestExecuteQueryWithLikeStringComparisons(t *testing.T) { resultKeys[i] = r.Key } - assert.DeepEqual(t, resultKeys, tc.expectKeys) + assert.Equal(t, resultKeys, tc.expectKeys) }) } } @@ -704,10 +704,10 @@ func TestExecuteQueryWithStringNotLikeComparisons(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { query, err := sql.ParseQuery(tc.query) - assert.NilError(t, err) + assert.Nil(t, err) result, err := sql.ExecuteQuery(&query, store.GetStore()) - assert.NilError(t, err) + assert.Nil(t, err) assert.Equal(t, len(result), tc.expectLen, "Expected %d results, got %d", tc.expectLen, len(result)) resultKeys := make([]string, len(result)) @@ -715,7 +715,7 @@ func TestExecuteQueryWithStringNotLikeComparisons(t *testing.T) { resultKeys[i] = r.Key } - assert.DeepEqual(t, resultKeys, tc.expectKeys) + assert.Equal(t, resultKeys, tc.expectKeys) }) } } diff --git a/internal/sql/fingerprint_test.go b/internal/sql/fingerprint_test.go index 38fecf40c..baedca8a9 100644 --- a/internal/sql/fingerprint_test.go +++ b/internal/sql/fingerprint_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/xwb1989/sqlparser" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" ) func TestExpressionString(t *testing.T) { @@ -112,7 +112,7 @@ func TestCombineOr(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.DeepEqual(t, tt.expected, combineOr(tt.a, tt.b)) + assert.Equal(t, tt.expected, combineOr(tt.a, tt.b)) }) } } @@ -207,7 +207,7 @@ func TestCombineAnd(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.DeepEqual(t, tt.expected, combineAnd(tt.a, tt.b)) + assert.Equal(t, tt.expected, combineAnd(tt.a, tt.b)) }) } } @@ -350,8 +350,8 @@ func TestGenerateFingerprintAndParseAstExpression(t *testing.T) { if err != nil { t.Fail() } - assert.DeepEqual(t, tt.expression, parseAstExpression(where).String()) - assert.DeepEqual(t, tt.fingerprint, generateFingerprint(where)) + assert.Equal(t, tt.expression, parseAstExpression(where).String()) + assert.Equal(t, tt.fingerprint, generateFingerprint(where)) } }) } From d9c41f20620478841875782ca59103baec67f817 Mon Sep 17 00:00:00 2001 From: Vansh Chopra <76000026+vanshavenger@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:07:20 +0530 Subject: [PATCH 07/20] #820 Select command docs (#1223) --- docs/src/content/docs/commands/AUTH.md | 2 +- docs/src/content/docs/commands/BF.EXISTS.md | 14 ++-- .../src/content/docs/commands/BGREWRITEAOF.md | 4 +- docs/src/content/docs/commands/BITCOUNT.md | 2 +- docs/src/content/docs/commands/BITOP.md | 10 +-- docs/src/content/docs/commands/BITPOS.md | 26 +++---- docs/src/content/docs/commands/DBSIZE.md | 22 ++++-- docs/src/content/docs/commands/DECR.md | 2 +- docs/src/content/docs/commands/DECRBY.md | 2 +- docs/src/content/docs/commands/DEL.md | 2 +- docs/src/content/docs/commands/ECHO.md | 2 +- docs/src/content/docs/commands/EXISTS.md | 2 +- docs/src/content/docs/commands/EXPIRE.md | 2 +- docs/src/content/docs/commands/EXPIREAT.md | 2 +- docs/src/content/docs/commands/EXPIRETIME.md | 2 +- docs/src/content/docs/commands/FLUSHDB.md | 74 ++++++++++++------- docs/src/content/docs/commands/GETBIT.md | 2 +- docs/src/content/docs/commands/GETDEL.md | 2 +- docs/src/content/docs/commands/GETEX.md | 2 +- docs/src/content/docs/commands/GETSET.md | 12 +-- docs/src/content/docs/commands/HGET.md | 10 +-- docs/src/content/docs/commands/HGETALL.md | 10 +-- docs/src/content/docs/commands/HLEN.md | 2 +- docs/src/content/docs/commands/HRANDFIELD.md | 18 ++--- docs/src/content/docs/commands/HSCAN.md | 2 +- docs/src/content/docs/commands/HSET.md | 14 ++-- docs/src/content/docs/commands/HSTRLEN.md | 2 +- docs/src/content/docs/commands/INCR.md | 2 +- docs/src/content/docs/commands/INCRBY.md | 2 +- docs/src/content/docs/commands/INCRBYFLOAT.md | 2 +- .../content/docs/commands/JSON.ARRAPPEND.md | 26 +++---- .../content/docs/commands/JSON.ARRINSERT.md | 42 +++++------ docs/src/content/docs/commands/JSON.ARRPOP.md | 16 ++-- .../src/content/docs/commands/JSON.ARRTRIM.md | 40 +++++----- docs/src/content/docs/commands/JSON.CLEAR.md | 2 +- docs/src/content/docs/commands/JSON.DEBUG.md | 18 ++--- docs/src/content/docs/commands/JSON.DEL.md | 30 ++++---- docs/src/content/docs/commands/JSON.FORGET.md | 2 +- docs/src/content/docs/commands/JSON.GET.md | 2 +- docs/src/content/docs/commands/JSON.MSET.md | 14 ++-- .../content/docs/commands/JSON.NUMINCRBY.md | 18 ++--- .../src/content/docs/commands/JSON.OBJKEYS.md | 28 +++---- docs/src/content/docs/commands/JSON.STRLEN.md | 2 +- docs/src/content/docs/commands/JSON.TOGGLE.md | 16 ++-- docs/src/content/docs/commands/JSON.TYPE.md | 2 +- docs/src/content/docs/commands/KEYS.md | 12 +-- docs/src/content/docs/commands/LATENCY.md | 2 +- docs/src/content/docs/commands/LLEN.md | 2 +- docs/src/content/docs/commands/LPOP.md | 2 +- docs/src/content/docs/commands/LPUSH.md | 10 +-- docs/src/content/docs/commands/MGET.md | 2 +- docs/src/content/docs/commands/MSET.md | 4 +- docs/src/content/docs/commands/OBJECT.md | 18 ++--- docs/src/content/docs/commands/PERSIST.md | 2 +- docs/src/content/docs/commands/PFADD.md | 2 +- docs/src/content/docs/commands/PFCOUNT.md | 2 +- docs/src/content/docs/commands/PFMERGE.md | 10 +-- docs/src/content/docs/commands/PING.md | 2 +- docs/src/content/docs/commands/PTTL.md | 2 +- docs/src/content/docs/commands/RENAME.md | 2 +- docs/src/content/docs/commands/RPOP.md | 2 +- docs/src/content/docs/commands/RPUSH.md | 2 +- docs/src/content/docs/commands/SADD.md | 2 +- docs/src/content/docs/commands/SCARD.md | 2 +- docs/src/content/docs/commands/SDIFF.md | 2 +- docs/src/content/docs/commands/SELECT.md | 35 ++++----- docs/src/content/docs/commands/SET.md | 2 +- docs/src/content/docs/commands/SETBIT.md | 16 ++-- docs/src/content/docs/commands/SINTER.md | 10 +-- docs/src/content/docs/commands/SREM.md | 12 +-- docs/src/content/docs/commands/TOUCH.md | 8 +- docs/src/content/docs/commands/ZCARD.md | 2 +- docs/src/content/docs/commands/ZCOUNT.md | 2 +- docs/src/content/docs/commands/ZPOPMAX.md | 2 +- docs/src/content/docs/commands/ZREM.md | 2 +- 75 files changed, 354 insertions(+), 331 deletions(-) diff --git a/docs/src/content/docs/commands/AUTH.md b/docs/src/content/docs/commands/AUTH.md index c2867b735..0fe23ea73 100644 --- a/docs/src/content/docs/commands/AUTH.md +++ b/docs/src/content/docs/commands/AUTH.md @@ -7,7 +7,7 @@ The `AUTH` command is a DiceDB command that enables you to authenticate a client ## Syntax -``` +```bash AUTH password ``` diff --git a/docs/src/content/docs/commands/BF.EXISTS.md b/docs/src/content/docs/commands/BF.EXISTS.md index 1625b07a9..c2711100b 100644 --- a/docs/src/content/docs/commands/BF.EXISTS.md +++ b/docs/src/content/docs/commands/BF.EXISTS.md @@ -50,9 +50,9 @@ The `BF.EXISTS` command can raise errors in the following scenarios: ### Checking for an existing item ```bash -127.0.0.1:6379> BF.ADD myBloomFilter "apple" +127.0.0.1:7379> BF.ADD myBloomFilter "apple" (integer) 1 -127.0.0.1:6379> BF.EXISTS myBloomFilter "apple" +127.0.0.1:7379> BF.EXISTS myBloomFilter "apple" (integer) 1 ``` @@ -61,7 +61,7 @@ In this example, the item "apple" is added to the Bloom Filter `myBloomFilter`. ### Checking for a non-existing item ```bash -127.0.0.1:6379> BF.EXISTS myBloomFilter "banana" +127.0.0.1:7379> BF.EXISTS myBloomFilter "banana" (integer) 0 ``` @@ -70,7 +70,7 @@ In this example, the item "banana" is checked in the Bloom Filter `myBloomFilter ### Handling a non-existing key ```bash -127.0.0.1:6379> BF.EXISTS nonExistingKey "apple" +127.0.0.1:7379> BF.EXISTS nonExistingKey "apple" (integer) 0 ``` @@ -79,9 +79,9 @@ In this example, the key `nonExistingKey` does not exist in the database. The co ### Handling a wrong type of key ```bash -127.0.0.1:6379> SET myString "hello" +127.0.0.1:7379> SET myString "hello" OK -127.0.0.1:6379> BF.EXISTS myString "apple" +127.0.0.1:7379> BF.EXISTS myString "apple" (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` @@ -90,7 +90,7 @@ In this example, the key `myString` is associated with a string value, not a Blo ### Incorrect number of arguments ```bash -127.0.0.1:6379> BF.EXISTS myBloomFilter +127.0.0.1:7379> BF.EXISTS myBloomFilter (error) ERR wrong number of arguments for 'bf.exists' command ``` diff --git a/docs/src/content/docs/commands/BGREWRITEAOF.md b/docs/src/content/docs/commands/BGREWRITEAOF.md index 2e93a608c..4a7f2e6a8 100644 --- a/docs/src/content/docs/commands/BGREWRITEAOF.md +++ b/docs/src/content/docs/commands/BGREWRITEAOF.md @@ -7,7 +7,7 @@ The `BGREWRITEAOF` command in DiceDB is used to asynchronously rewrite the Appen ## Syntax -``` +```bash BGREWRITEAOF ``` @@ -42,7 +42,7 @@ When the `BGREWRITEAOF` command is issued, DiceDB performs the following steps: ## Example Usage ### Basic Usage -```sh +```bash 127.0.0.1:7379> BGREWRITEAOF OK ``` diff --git a/docs/src/content/docs/commands/BITCOUNT.md b/docs/src/content/docs/commands/BITCOUNT.md index f8ff0e540..fb6fb93d6 100644 --- a/docs/src/content/docs/commands/BITCOUNT.md +++ b/docs/src/content/docs/commands/BITCOUNT.md @@ -7,7 +7,7 @@ The `BITCOUNT` command in DiceDB is used to count the number of set bits (i.e., ## Syntax -``` +```bash BITCOUNT key [start end [BYTE | BIT]] ``` diff --git a/docs/src/content/docs/commands/BITOP.md b/docs/src/content/docs/commands/BITOP.md index ecfdc63d5..239efeb3e 100644 --- a/docs/src/content/docs/commands/BITOP.md +++ b/docs/src/content/docs/commands/BITOP.md @@ -7,7 +7,7 @@ The `BITOP` command in DiceDB is used to perform bitwise operations between stri ## Syntax -```plaintext +```bash BITOP operation destkey key [key ...] ``` @@ -60,7 +60,7 @@ The `BITOP` command can raise errors in the following cases: ### Example 1: Bitwise AND Operation -```plaintext +```bash SET key1 "foo" SET key2 "bar" BITOP AND result key1 key2 @@ -76,7 +76,7 @@ GET result ### Example 2: Bitwise OR Operation -```plaintext +```bash SET key1 "foo" SET key2 "bar" BITOP OR result key1 key2 @@ -92,7 +92,7 @@ GET result ### Example 3: Bitwise XOR Operation -```plaintext +```bash SET key1 "foo" SET key2 "bar" BITOP XOR result key1 key2 @@ -108,7 +108,7 @@ GET result ### Example 4: Bitwise NOT Operation -```plaintext +```bash SET key1 "foo" BITOP NOT result key1 GET result diff --git a/docs/src/content/docs/commands/BITPOS.md b/docs/src/content/docs/commands/BITPOS.md index 5cc85b895..9fb738997 100644 --- a/docs/src/content/docs/commands/BITPOS.md +++ b/docs/src/content/docs/commands/BITPOS.md @@ -7,7 +7,7 @@ The `BITPOS` command in DiceDB is used to find the position of the first bit set ## Syntax -```plaintext +```bash BITPOS key bit [start] [end] ``` @@ -49,14 +49,14 @@ The `BITPOS` command can raise errors in the following cases: Find the position of the first bit set to 1 in the string stored at key `mykey`: -```plaintext +```bash SET mykey "foobar" BITPOS mykey 1 ``` `Output`: -```plaintext +```bash 6 ``` @@ -64,14 +64,14 @@ BITPOS mykey 1 Find the position of the first bit set to 0 in the string stored at key `mykey`, starting from byte position 2 and ending at byte position 4: -```plaintext +```bash SET mykey "foobar" BITPOS mykey 0 2 4 ``` `Output`: -```plaintext +```bash 16 ``` @@ -79,14 +79,14 @@ BITPOS mykey 0 2 4 If the specified bit is not found within the specified range, the command returns -1: -```plaintext +```bash SET mykey "foobar" BITPOS mykey 1 2 4 ``` `Output`: -```plaintext +```bash -1 ``` @@ -96,14 +96,14 @@ BITPOS mykey 1 2 4 Attempting to use `BITPOS` on a key that holds a non-string value: -```plaintext +```bash LPUSH mylist "item" BITPOS mylist 1 ``` `Output`: -```plaintext +```bash (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` @@ -111,14 +111,14 @@ BITPOS mylist 1 Using a bit value other than 0 or 1: -```plaintext +```bash SET mykey "foobar" BITPOS mykey 2 ``` `Output`: -```plaintext +```bash (error) ERR bit is not an integer or out of range ``` @@ -126,13 +126,13 @@ BITPOS mykey 2 Using non-integer values for the `start` or `end` parameters: -```plaintext +```bash SET mykey "foobar" BITPOS mykey 1 "a" "b" ``` `Output`: -```plaintext +```bash (error) ERR value is not an integer or out of range ``` diff --git a/docs/src/content/docs/commands/DBSIZE.md b/docs/src/content/docs/commands/DBSIZE.md index 92302c960..ee9d0b9a6 100644 --- a/docs/src/content/docs/commands/DBSIZE.md +++ b/docs/src/content/docs/commands/DBSIZE.md @@ -3,11 +3,13 @@ title: DBSIZE description: The `DBSIZE` command in DiceDB returns the number of keys in the currently selected database, providing a quick way to understand the size of your database. --- +> **Important Note:** As of the current version, DiceDB does not support multiple databases. Therefore, while the documentation mentions database selection via the `SELECT` command, all operations occur on a single database space. + The `DBSIZE` command in DiceDB is used to return the number of keys in the currently selected database. This command is useful for monitoring and managing the size of your DiceDB database, providing a quick way to understand the number of keys stored. ## Syntax -``` +```bash DBSIZE ``` @@ -31,8 +33,8 @@ The `DBSIZE` command does not take any parameters. - If multiple databases are in use, `DBSIZE` will only count keys in the currently selected database. ## Errors -The `DBSIZE` command is straightforward and does not typically result in errors under normal usage. However, there are a few scenarios where errors might be encountered: +The `DBSIZE` command is straightforward and does not typically result in errors under normal usage. However, there are a few scenarios where errors might be encountered: 1. `Connection Issues`: - Error Message: `ERR Connection lost` @@ -48,7 +50,7 @@ The `DBSIZE` command is straightforward and does not typically result in errors Getting the number of keys in the currently selected database: -```shell +```bash 127.0.0.1:7379> DBSIZE (integer) 42 ``` @@ -57,9 +59,9 @@ In this example, the currently selected database contains 42 keys. ### Using with Multiple Databases -If you are working with multiple databases, you can switch between them using the `SELECT` command and then use `DBSIZE` to get the number of keys in each database: +While the following example shows the traditional syntax for working with multiple databases, please note that in the current version of DiceDB, all operations occur on a single database space: -```shell +```bash 127.0.0.1:7379> SELECT 0 OK 127.0.0.1:7379> DBSIZE @@ -68,16 +70,20 @@ OK 127.0.0.1:7379> SELECT 1 OK 127.0.0.1:7379> DBSIZE -(integer) 15 +(integer) 42 ``` -In this example, database 0 contains 42 keys, and database 1 contains 15 keys. +In the current implementation, both commands will return the same count as they operate on the same database space. ### Error Scenarios 1. Attempting to use `DBSIZE` without proper authentication: -```shell +```bash 127.0.0.1:7379> DBSIZE (error) NOAUTH Authentication required ``` + +## Notes + +As mentioned at the beginning of this document, the current version of DiceDB operates on a single database space. While the `SELECT` command is available as a placeholder, switching databases will not affect the operation of the `DBSIZE` command, and it will always return the count of keys from the single available database space. \ No newline at end of file diff --git a/docs/src/content/docs/commands/DECR.md b/docs/src/content/docs/commands/DECR.md index 73d6f941b..694ab44f8 100644 --- a/docs/src/content/docs/commands/DECR.md +++ b/docs/src/content/docs/commands/DECR.md @@ -7,7 +7,7 @@ The `DECR` command in DiceDB is used to decrement the integer value of a key by ## Syntax -```plaintext +```bash DECR key ``` diff --git a/docs/src/content/docs/commands/DECRBY.md b/docs/src/content/docs/commands/DECRBY.md index 0372b876e..30b949313 100644 --- a/docs/src/content/docs/commands/DECRBY.md +++ b/docs/src/content/docs/commands/DECRBY.md @@ -7,7 +7,7 @@ The `DECRBY` command in DiceDB is used to decrement the integer value of a key b ## Syntax -``` +```bash DECRBY key delta ``` diff --git a/docs/src/content/docs/commands/DEL.md b/docs/src/content/docs/commands/DEL.md index 525b377cc..744d1abe3 100644 --- a/docs/src/content/docs/commands/DEL.md +++ b/docs/src/content/docs/commands/DEL.md @@ -9,7 +9,7 @@ The `DEL` command in DiceDB is used to remove one or more keys from the database ## Syntax -``` +```bash DEL key [key ...] ``` diff --git a/docs/src/content/docs/commands/ECHO.md b/docs/src/content/docs/commands/ECHO.md index edcd4f537..a94f8f9c1 100644 --- a/docs/src/content/docs/commands/ECHO.md +++ b/docs/src/content/docs/commands/ECHO.md @@ -5,7 +5,7 @@ description: The `ECHO` command in DiceDB is used to print a message ### Syntax -``` +```bash ECHO message ``` diff --git a/docs/src/content/docs/commands/EXISTS.md b/docs/src/content/docs/commands/EXISTS.md index d463911b5..fcb52e8f8 100644 --- a/docs/src/content/docs/commands/EXISTS.md +++ b/docs/src/content/docs/commands/EXISTS.md @@ -6,7 +6,7 @@ description: The `EXISTS` command in DiceDB is used to determine if one or more The `EXISTS` command in DiceDB is used to determine if one or more specified keys exist in the database. It returns the number of keys that exist among the specified ones. ## Syntax -``` +```bash EXISTS key [key ...] ``` diff --git a/docs/src/content/docs/commands/EXPIRE.md b/docs/src/content/docs/commands/EXPIRE.md index 4d94460b6..e8612d2cf 100644 --- a/docs/src/content/docs/commands/EXPIRE.md +++ b/docs/src/content/docs/commands/EXPIRE.md @@ -7,7 +7,7 @@ The `EXPIRE` command in DiceDB is used to set a timeout on a specified key. Afte ## Syntax -``` +```bash EXPIRE key seconds [NX | XX | GT | LT] ``` diff --git a/docs/src/content/docs/commands/EXPIREAT.md b/docs/src/content/docs/commands/EXPIREAT.md index 66cdac48a..b5226f0b1 100644 --- a/docs/src/content/docs/commands/EXPIREAT.md +++ b/docs/src/content/docs/commands/EXPIREAT.md @@ -7,7 +7,7 @@ The `EXPIREAT` command is used to set the expiration time of a key in DiceDB. Un ## Syntax -```plaintext +```bash EXPIREAT key timestamp [NX|XX|GT|LT] ``` diff --git a/docs/src/content/docs/commands/EXPIRETIME.md b/docs/src/content/docs/commands/EXPIRETIME.md index 9e94d9d7c..1c226a08b 100644 --- a/docs/src/content/docs/commands/EXPIRETIME.md +++ b/docs/src/content/docs/commands/EXPIRETIME.md @@ -7,7 +7,7 @@ The `EXPIRETIME` command in DiceDB is used to retrieve the absolute Unix timesta ## Syntax -``` +```bash EXPIRETIME key ``` diff --git a/docs/src/content/docs/commands/FLUSHDB.md b/docs/src/content/docs/commands/FLUSHDB.md index 1f3877002..cb82cec8d 100644 --- a/docs/src/content/docs/commands/FLUSHDB.md +++ b/docs/src/content/docs/commands/FLUSHDB.md @@ -1,19 +1,28 @@ --- title: FLUSHDB -description: Documentation for the DiceDB command FLUSHDB +description: The FLUSHDB command removes all keys from the currently selected database, providing a way to clear all data in a specific database space. --- The `FLUSHDB` command is used to remove all keys from the currently selected database in a DiceDB instance. This command is useful when you need to clear all the data in a specific database without affecting other databases in the same DiceDB instance. +## Syntax + +```bash +FLUSHDB +``` + ## Parameters The `FLUSHDB` command does not take any parameters. -## Return Value - -The `FLUSHDB` command returns a simple string reply: +## Return Values -- `OK`: Indicates that the operation was successful and all keys in the current database have been removed. +| Condition | Return Value | +|--------------------------|--------------| +| Command is successful | `OK` | +| Authentication required | Error: `NOAUTH Authentication required` | +| Permission denied | Error: `NOPERM this user has no permissions to run the 'flushdb' command` | +| Read-only mode | Error: `READONLY You can't write against a read-only replica` | ## Behaviour @@ -27,46 +36,57 @@ When the `FLUSHDB` command is executed, the following actions occur: The `FLUSHDB` command is straightforward and does not typically raise errors under normal circumstances. However, there are a few scenarios where issues might arise: -1. `Permission Denied`: If the DiceDB instance is configured with ACLs (Access Control Lists) and the user does not have the necessary permissions to execute the `FLUSHDB` command, an error will be raised. +1. `Authentication Issues`: + - Error Message: `(error) NOAUTH Authentication required` + - Occurs when authentication is required but not provided - - Error: `(error) NOAUTH Authentication required.` or `(error) NOPERM this user has no permissions to run the 'flushdb' command` +2. `Permission Issues`: + - Error Message: `(error) NOPERM this user has no permissions to run the 'flushdb' command` + - Occurs when the user lacks necessary permissions to execute the command -2. `Read-Only Mode`: If the DiceDB instance is in read-only mode (e.g., a read-only replica), the `FLUSHDB` command will not be allowed. - - - Error: `(error) READONLY You can't write against a read-only replica.` +3. `Read-Only Mode`: + - Error Message: `(error) READONLY You can't write against a read-only replica` + - Occurs when attempting to execute on a read-only instance ## Example Usage ### Basic Usage -To clear all keys from the currently selected database: - -```sh -127.0.0.1:6379> FLUSHDB +```bash +127.0.0.1:7379> FLUSHDB OK ``` -### Using with Multiple Databases - -If you have multiple databases and want to clear a specific one, you need to select the database first using the `SELECT` command, then execute `FLUSHDB`: +### Verifying Empty Database -```sh -127.0.0.1:6379> SELECT 1 -OK -127.0.0.1:6379[1]> FLUSHDB +```bash +127.0.0.1:7379> FLUSHDB OK +127.0.0.1:7379> DBSIZE +(integer) 0 ``` -### Checking the Result +### Example with SELECT Command -After executing `FLUSHDB`, you can verify that the database is empty by using the `DBSIZE` command, which returns the number of keys in the currently selected database: +While the following example shows the traditional syntax for working with multiple databases, please note that in the current version, all operations occur on a single database space: -```sh -127.0.0.1:6379> DBSIZE +```bash +127.0.0.1:7379> SELECT 1 +OK +127.0.0.1:7379> FLUSHDB +OK +127.0.0.1:7379> DBSIZE (integer) 0 ``` +## Best Practices + +- Always verify that you're operating on the intended database before executing FLUSHDB +- Consider using backup mechanisms before executing FLUSHDB in production environments +- Use appropriate access controls to restrict FLUSHDB usage to authorized users only + ## Notes -- `Data Loss`: The `FLUSHDB` command will result in the loss of all data in the selected database. Use this command with caution, especially in production environments. -- `Atomic Operation`: The `FLUSHDB` command is atomic, meaning that all keys are removed in a single operation without any intermediate states. +- The current version of DiceDB operates on a single database space. While the `SELECT` command is available as a placeholder, switching databases will not affect the operation of the `FLUSHDB` command, and it will always clear the keys from the single available database space. + +- The command is particularly powerful and should be used with caution as it results in immediate, irreversible data loss for all keys in the database. \ No newline at end of file diff --git a/docs/src/content/docs/commands/GETBIT.md b/docs/src/content/docs/commands/GETBIT.md index c59057f55..c1306bae0 100644 --- a/docs/src/content/docs/commands/GETBIT.md +++ b/docs/src/content/docs/commands/GETBIT.md @@ -7,7 +7,7 @@ The `GETBIT` command is used to retrieve the bit value at a specified offset in ## Syntax -``` +```bash GETBIT key offset ``` diff --git a/docs/src/content/docs/commands/GETDEL.md b/docs/src/content/docs/commands/GETDEL.md index 7cdd54ad5..88ebf1255 100644 --- a/docs/src/content/docs/commands/GETDEL.md +++ b/docs/src/content/docs/commands/GETDEL.md @@ -7,7 +7,7 @@ The `GETDEL` command in DiceDB is used to retrieve the value of a specified key ## Syntax -``` +```bash GETDEL key ``` diff --git a/docs/src/content/docs/commands/GETEX.md b/docs/src/content/docs/commands/GETEX.md index 7a83f76ac..50ad4a013 100644 --- a/docs/src/content/docs/commands/GETEX.md +++ b/docs/src/content/docs/commands/GETEX.md @@ -7,7 +7,7 @@ The `GETEX` command in DiceDB is used to retrieve the value of a specified key a ## Syntax -``` +```bash GETEX key [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|PERSIST] ``` diff --git a/docs/src/content/docs/commands/GETSET.md b/docs/src/content/docs/commands/GETSET.md index 39291d5b8..24bd71090 100644 --- a/docs/src/content/docs/commands/GETSET.md +++ b/docs/src/content/docs/commands/GETSET.md @@ -7,7 +7,7 @@ The `GETSET` command in DiceDB is a powerful atomic operation that combines the ## Syntax -``` +```bash GETSET key value ``` @@ -53,7 +53,7 @@ The `GETSET` command can raise errors in the following scenarios: ### Basic Example -```DiceDB +```bash 127.0.0.1:7379> SET mykey "Hello" 127.0.0.1:7379> GETSET mykey "World" "Hello" @@ -67,7 +67,7 @@ The `GETSET` command can raise errors in the following scenarios: ### Example with Non-Existent Key -```DiceDB +```bash 127.0.0.1:7379> GETSET newkey "NewValue" (nil) ``` @@ -80,7 +80,7 @@ The `GETSET` command can raise errors in the following scenarios: ### Example with Key having pre-existing TTL -```DiceDB +```bash 127.0.0.1:7379> SET newkey "test" OK 127.0.0.1:7379> EXPIRE newkey 60 @@ -102,7 +102,7 @@ OK ### Error Example: Wrong Type -```DiceDB +```bash 127.0.0.1:7379> LPUSH mylist "item" 127.0.0.1:7379> GETSET mylist "NewValue" (error) ERROR WRONGTYPE Operation against a key holding the wrong kind of value @@ -115,7 +115,7 @@ OK ### Error Example: Syntax Error -```DiceDB +```bash 127.0.0.1:7379> GETSET mykey (error) ERROR wrong number of arguments for 'getset' command ``` diff --git a/docs/src/content/docs/commands/HGET.md b/docs/src/content/docs/commands/HGET.md index f3bb46dc3..47195a91d 100644 --- a/docs/src/content/docs/commands/HGET.md +++ b/docs/src/content/docs/commands/HGET.md @@ -7,7 +7,7 @@ The `HGET` command in DiceDB is used to retrieve the value associated with a spe ## Syntax -``` +```bash HGET key field ``` @@ -38,7 +38,7 @@ When the `HGET` command is executed, DiceDB performs the following steps: ### Example 1: Retrieving a value from a hash -```DiceDB +```bash HSET user:1000 name "John Doe" HSET user:1000 age "30" HGET user:1000 name @@ -52,7 +52,7 @@ HGET user:1000 name ### Example 2: Field does not exist -```DiceDB +```bash HGET user:1000 email ``` @@ -64,7 +64,7 @@ HGET user:1000 email ### Example 3: Key does not exist -```DiceDB +```bash HGET user:2000 name ``` @@ -76,7 +76,7 @@ HGET user:2000 name ### Example 4: Key is not a hash -```DiceDB +```bash SET user:3000 "Not a hash" HGET user:3000 name ``` diff --git a/docs/src/content/docs/commands/HGETALL.md b/docs/src/content/docs/commands/HGETALL.md index b08b8b07d..b3b2da145 100644 --- a/docs/src/content/docs/commands/HGETALL.md +++ b/docs/src/content/docs/commands/HGETALL.md @@ -7,7 +7,7 @@ The `HGETALL` command in DiceDB is used to retrieve all the fields and values of ## Syntax -``` +```bash HGETALL key ``` @@ -46,7 +46,7 @@ The `HGETALL` command can raise the following errors: ### Example 1: Retrieving all fields and values from an existing hash -```DiceDB +```bash 127.0.0.1:7379> HSET user:1000 name "John Doe" age "30" country "USA" (integer) 3 127.0.0.1:7379> HGETALL user:1000 @@ -65,7 +65,7 @@ The `HGETALL` command can raise the following errors: ### Example 2: Retrieving from a non-existing key -```DiceDB +```bash 127.0.0.1:7379> HGETALL user:2000 ``` @@ -78,7 +78,7 @@ The `HGETALL` command can raise the following errors: ### Error Example: Key is not a hash -```DiceDB +```bash 127.0.0.1:7379> SET user:3000 "This is a string" OK 127.0.0.1:7379> HGETALL user:3000 @@ -93,7 +93,7 @@ OK ### Error Example: Invalid number of arguments are passed -```DiceDB +```bash 127.0.0.1:7379> HGETALL user:3000 helloworld ``` `Output:` diff --git a/docs/src/content/docs/commands/HLEN.md b/docs/src/content/docs/commands/HLEN.md index c8ef24a03..6e8168995 100644 --- a/docs/src/content/docs/commands/HLEN.md +++ b/docs/src/content/docs/commands/HLEN.md @@ -7,7 +7,7 @@ The `HLEN` command in DiceDB is used to obtain the number of fields contained wi ## Syntax -``` +```bash HLEN key ``` diff --git a/docs/src/content/docs/commands/HRANDFIELD.md b/docs/src/content/docs/commands/HRANDFIELD.md index afcbf2ac2..2fe1fdc55 100644 --- a/docs/src/content/docs/commands/HRANDFIELD.md +++ b/docs/src/content/docs/commands/HRANDFIELD.md @@ -7,7 +7,7 @@ The `HRANDFIELD` command in DiceDB is used to return one or more random fields f ## Syntax -``` +```bash HRANDFIELD key [count [WITHVALUES]] ``` @@ -54,16 +54,16 @@ HRANDFIELD key [count [WITHVALUES]] ### Basic Usage Executing `HRANDFIELD` on a key without any parameters ```bash -127.0.0.1:6379> HSET keys field1 value1 field2 value2 field3 value3 +127.0.0.1:7379> HSET keys field1 value1 field2 value2 field3 value3 (integer) 3 -127.0.0.1:6379> HRANDFIELD keys +127.0.0.1:7379> HRANDFIELD keys "field1" ``` ### Usage with `count` parameter Executing `HRANDFIELD` on a key with a `count` parameter of 2 ```bash -127.0.0.1:6379> HRANDFIELD keys 2 +127.0.0.1:7379> HRANDFIELD keys 2 1) "field2" 2) "field1" ``` @@ -71,7 +71,7 @@ Executing `HRANDFIELD` on a key with a `count` parameter of 2 ### Usage with `WITHVALUES` parameter Executing `HRANDFIELD` with the `WITHVALUES` parameter ```bash -127.0.0.1:6379> HRANDFIELD keys 2 WITHVALUES +127.0.0.1:7379> HRANDFIELD keys 2 WITHVALUES 1) "field2" 2) "value2" 3) "field1" @@ -82,9 +82,9 @@ Executing `HRANDFIELD` with the `WITHVALUES` parameter Executing `hrandfield` on a non-hash key ```bash -127.0.0.1:6379> SET key "not a hash" +127.0.0.1:7379> SET key "not a hash" OK -127.0.0.1:6379> HRANDFIELD key +127.0.0.1:7379> HRANDFIELD key (error) WRONGTYPE Operation against a key holding the wrong kind of value ``` @@ -92,14 +92,14 @@ OK Non-integer value passed as `count` ```bash -127.0.0.1:6379> HRANDFIELD keys hello +127.0.0.1:7379> HRANDFIELD keys hello (error) ERROR value is not an integer or out of range ``` ### Invalid number of arguments Passing invalid number of arguments to the `hrandfield` command ```bash -127.0.0.1:6379> HRANDFIELD +127.0.0.1:7379> HRANDFIELD (error) ERR wrong number of arguments for 'hrandfield' command ``` diff --git a/docs/src/content/docs/commands/HSCAN.md b/docs/src/content/docs/commands/HSCAN.md index 9177eea7a..b6254bac7 100644 --- a/docs/src/content/docs/commands/HSCAN.md +++ b/docs/src/content/docs/commands/HSCAN.md @@ -7,7 +7,7 @@ The `HSCAN` command is used to incrementally iterate over the fields of a hash s ## Syntax -``` +```bash HSCAN key cursor [MATCH pattern] [COUNT count] ``` diff --git a/docs/src/content/docs/commands/HSET.md b/docs/src/content/docs/commands/HSET.md index 32a1ec593..32ed2ed39 100644 --- a/docs/src/content/docs/commands/HSET.md +++ b/docs/src/content/docs/commands/HSET.md @@ -7,7 +7,7 @@ The `HSET` command in DiceDB is used to set the value of a field in a hash. If t ## Syntax -``` +```bash HSET key field value [field value ...] ``` @@ -42,7 +42,7 @@ The `HSET` command can raise errors in the following scenarios: ### Basic Example -```DiceDB +```bash HSET user:1000 name "John Doe" age 30 ``` @@ -50,7 +50,7 @@ This command sets the `name` field to "John Doe" and the `age` field to 30 in th ### Multiple Field-Value Pairs -```DiceDB +```bash HSET user:1000 name "John Doe" age 30 email "john.doe@example.com" ``` @@ -58,7 +58,7 @@ This command sets the `name`, `age`, and `email` fields in the hash stored at `u ### Updating Existing Fields -```DiceDB +```bash HSET user:1000 age 31 ``` @@ -68,7 +68,7 @@ This command updates the `age` field to 31 in the hash stored at `user:1000`. If ### Creating a New Hash -```DiceDB +```bash HSET product:2000 name "Laptop" price 999.99 stock 50 ``` @@ -77,7 +77,7 @@ HSET product:2000 name "Laptop" price 999.99 stock 50 ### Updating an Existing Hash -```DiceDB +```bash HSET product:2000 price 899.99 stock 45 ``` @@ -86,7 +86,7 @@ HSET product:2000 price 899.99 stock 45 ### Error Handling Example -```DiceDB +```bash SET product:2000 "This is a string" HSET product:2000 name "Laptop" ``` diff --git a/docs/src/content/docs/commands/HSTRLEN.md b/docs/src/content/docs/commands/HSTRLEN.md index a09279939..77add9437 100644 --- a/docs/src/content/docs/commands/HSTRLEN.md +++ b/docs/src/content/docs/commands/HSTRLEN.md @@ -7,7 +7,7 @@ The `HSTRLEN` command in DiceDB is used to obtain the string length of value ass ## Syntax -``` +```bash HSTRLEN key field ``` diff --git a/docs/src/content/docs/commands/INCR.md b/docs/src/content/docs/commands/INCR.md index 2e2c0c463..7fceadd83 100644 --- a/docs/src/content/docs/commands/INCR.md +++ b/docs/src/content/docs/commands/INCR.md @@ -7,7 +7,7 @@ The `INCR` command in DiceDB is used to increment the integer value of a key by ## Syntax -```plaintext +```bash INCR key ``` diff --git a/docs/src/content/docs/commands/INCRBY.md b/docs/src/content/docs/commands/INCRBY.md index 9ebc383ec..526ae9e7f 100644 --- a/docs/src/content/docs/commands/INCRBY.md +++ b/docs/src/content/docs/commands/INCRBY.md @@ -7,7 +7,7 @@ The `INCRBY` command in DiceDB is used to increment the integer value of a key b ## Syntax -``` +```bash INCRBY key delta ``` diff --git a/docs/src/content/docs/commands/INCRBYFLOAT.md b/docs/src/content/docs/commands/INCRBYFLOAT.md index fcdd293a7..5053a93f3 100644 --- a/docs/src/content/docs/commands/INCRBYFLOAT.md +++ b/docs/src/content/docs/commands/INCRBYFLOAT.md @@ -7,7 +7,7 @@ The `INCRBYFLOAT` command in DiceDB is used to increment the numeric value of a ## Syntax -``` +```bash INCRBYFLOAT key delta ``` diff --git a/docs/src/content/docs/commands/JSON.ARRAPPEND.md b/docs/src/content/docs/commands/JSON.ARRAPPEND.md index e962eb719..9ae4716c6 100644 --- a/docs/src/content/docs/commands/JSON.ARRAPPEND.md +++ b/docs/src/content/docs/commands/JSON.ARRAPPEND.md @@ -48,56 +48,56 @@ When the `JSON.ARRAPPEND` command is executed, the specified JSON values are app ### Example 1: Appending a single value to an array ```bash -127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRAPPEND myjson .numbers 4 +127.0.0.1:7379> JSON.ARRAPPEND myjson .numbers 4 (integer) 4 -127.0.0.1:6379> JSON.GET myjson +127.0.0.1:7379> JSON.GET myjson "{\"numbers\":[1,2,3,4]}" ``` ### Example 2: Appending multiple values to an array ```bash -127.0.0.1:6379> JSON.SET myjson . '{"fruits": ["apple", "banana"]}' +127.0.0.1:7379> JSON.SET myjson . '{"fruits": ["apple", "banana"]}' OK -127.0.0.1:6379> JSON.ARRAPPEND myjson .fruits "cherry" "date" +127.0.0.1:7379> JSON.ARRAPPEND myjson .fruits "cherry" "date" (integer) 4 -127.0.0.1:6379> JSON.GET myjson +127.0.0.1:7379> JSON.GET myjson "{\"fruits\":[\"apple\",\"banana\",\"cherry\",\"date\"]}" ``` ### Example 3: Error when key does not exist ```bash -127.0.0.1:6379> JSON.ARRAPPEND nonexistingkey .array 1 +127.0.0.1:7379> JSON.ARRAPPEND nonexistingkey .array 1 (error) ERR key does not exist ``` ### Example 4: Error when path does not exist ```bash -127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRAPPEND myjson .nonexistingpath 4 +127.0.0.1:7379> JSON.ARRAPPEND myjson .nonexistingpath 4 (error) ERR path .nonexistingpath does not exist ``` ### Example 5: Error when path is not an array ```bash -127.0.0.1:6379> JSON.SET myjson . '{"object": {"key": "value"}}' +127.0.0.1:7379> JSON.SET myjson . '{"object": {"key": "value"}}' OK -127.0.0.1:6379> JSON.ARRAPPEND myjson .object 4 +127.0.0.1:7379> JSON.ARRAPPEND myjson .object 4 (error) ERR path is not an array ``` ### Example 6: Error when invalid JSON is provided ```bash -127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRAPPEND myjson .numbers invalidjson +127.0.0.1:7379> JSON.ARRAPPEND myjson .numbers invalidjson (error) ERR invalid JSON ``` diff --git a/docs/src/content/docs/commands/JSON.ARRINSERT.md b/docs/src/content/docs/commands/JSON.ARRINSERT.md index 51ff03bff..fa4729543 100644 --- a/docs/src/content/docs/commands/JSON.ARRINSERT.md +++ b/docs/src/content/docs/commands/JSON.ARRINSERT.md @@ -3,7 +3,7 @@ title: JSON.ARRINSERT description: The JSON.ARRINSERT command in DiceDB is used to insert one or more JSON values into an array at a specified path before a given index. This command shifts all existing elements in the array to the right, making room for the new elements. --- -The JSON.ARRINSERT command allows you to insert one or more JSON values into an array at a specified path before a given index. All existing elements in the array are shifted to the right to make room for the new elements. +The `JSON.ARRINSERT` command allows you to insert one or more JSON values into an array at a specified path before a given index. All existing elements in the array are shifted to the right to make room for the new elements. This command returns an array of integer replies for each path, where each integer represents the new size of the array after the insertion. If the path does not exist or is not an array, it returns an error. @@ -72,12 +72,12 @@ JSON.ARRINSERT [value ...] Inserting at a valid index in the root path -```plaintext -127.0.0.1:6379> JSON.SET a $ '[1,2]' +```bash +127.0.0.1:7379> JSON.SET a $ '[1,2]' OK -127.0.0.1:6379> JSON.ARRINSERT a $ 2 3 4 5 +127.0.0.1:7379> JSON.ARRINSERT a $ 2 3 4 5 (integer) 5 -127.0.0.1:6379> JSON.GET a +127.0.0.1:7379> JSON.GET a [1,2,3,4,5] ``` @@ -85,12 +85,12 @@ OK Inserting at a negative index -```plaintext -127.0.0.1:6379> JSON.SET a $ '[1,2]' +```bash +127.0.0.1:7379> JSON.SET a $ '[1,2]' OK -127.0.0.1:6379> JSON.ARRINSERT a $ -2 3 4 5 +127.0.0.1:7379> JSON.ARRINSERT a $ -2 3 4 5 (integer) 5 -127.0.0.1:6379> JSON.GET a +127.0.0.1:7379> JSON.GET a [3,4,5,1,2] ``` @@ -98,12 +98,12 @@ OK Handling nested arrays -```plaintext -127.0.0.1:6379> JSON.SET b $ '{"name":"tom","score":[10,20],"partner2":{"score":[10,20]}}' +```bash +127.0.0.1:7379> JSON.SET b $ '{"name":"tom","score":[10,20],"partner2":{"score":[10,20]}}' OK -127.0.0.1:6379> JSON.ARRINSERT b $..score 1 5 6 true +127.0.0.1:7379> JSON.ARRINSERT b $..score 1 5 6 true (integer) 5 -127.0.0.1:6379> JSON.GET b +127.0.0.1:7379> JSON.GET b {"name":"tom","score":[10,5,6,true,20],"partner2":{"score":[10,5,6,true,20]}} ``` @@ -111,12 +111,12 @@ OK Inserting with an out-of-bounds index -```plaintext -127.0.0.1:6379> JSON.SET a $ '[1,2]' +```bash +127.0.0.1:7379> JSON.SET a $ '[1,2]' OK -127.0.0.1:6379> JSON.ARRINSERT a $ 4 3 +127.0.0.1:7379> JSON.ARRINSERT a $ 4 3 ERR index out of bounds -127.0.0.1:6379> JSON.GET a +127.0.0.1:7379> JSON.GET a [1,2] ``` @@ -124,11 +124,11 @@ ERR index out of bounds Invalid index type -```plaintext -127.0.0.1:6379> JSON.SET a $ '[1,2]' +```bash +127.0.0.1:7379> JSON.SET a $ '[1,2]' OK -127.0.0.1:6379> JSON.ARRINSERT a $ ss 3 +127.0.0.1:7379> JSON.ARRINSERT a $ ss 3 ERR value is not an integer or out of range -127.0.0.1:6379> JSON.GET a +127.0.0.1:7379> JSON.GET a [1,2] ``` diff --git a/docs/src/content/docs/commands/JSON.ARRPOP.md b/docs/src/content/docs/commands/JSON.ARRPOP.md index fc8aa2ed8..446bfc776 100644 --- a/docs/src/content/docs/commands/JSON.ARRPOP.md +++ b/docs/src/content/docs/commands/JSON.ARRPOP.md @@ -50,36 +50,36 @@ When the `JSON.ARRPOP` command is executed, the specified element is popped from ### Popping value from an array ```bash -127.0.0.1:6379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRPOP myjson .numbers 1 +127.0.0.1:7379> JSON.ARRPOP myjson .numbers 1 (integer) 2 -127.0.0.1:6379> JSON.GET myjson +127.0.0.1:7379> JSON.GET myjson "{\"numbers\":[1,3]}" ``` ### Error when key does not exist ```bash -127.0.0.1:6379> JSON.ARRPOP nonexistingkey .array 1 +127.0.0.1:7379> 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]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRPOP myjson .nonexistingpath 4 +127.0.0.1:7379> 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]}' +127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK -127.0.0.1:6379> JSON.ARRPOP myjson .numbers 4 +127.0.0.1:7379> JSON.ARRPOP myjson .numbers 4 (error) ERR path is not an array ``` diff --git a/docs/src/content/docs/commands/JSON.ARRTRIM.md b/docs/src/content/docs/commands/JSON.ARRTRIM.md index a6aaf3887..9b3d10e9a 100644 --- a/docs/src/content/docs/commands/JSON.ARRTRIM.md +++ b/docs/src/content/docs/commands/JSON.ARRTRIM.md @@ -72,40 +72,40 @@ JSON.ARRTRIM Trimming an array to a specific range -```plaintext -127.0.0.1:6379> JSON.SET b $ '[1, 2, 3, 4, 5]' +```bash +127.0.0.1:7379> JSON.SET b $ '[1, 2, 3, 4, 5]' "OK" -127.0.0.1:6379> JSON.ARRTRIM b $ 1 3 +127.0.0.1:7379> JSON.ARRTRIM b $ 1 3 1) "3" -127.0.0.1:6379> JSON.GET b +127.0.0.1:7379> JSON.GET b "[2,3,4]" -127.0.0.1:6379> +127.0.0.1:7379> ``` ### Trim array down to 1 element Trimming an array to a single element -```plaintext -127.0.0.1:6379> JSON.SET a $ '[0,1,2]' +```bash +127.0.0.1:7379> JSON.SET a $ '[0,1,2]' "OK" -127.0.0.1:6379> JSON.ARRTRIM a $ 1 1 +127.0.0.1:7379> JSON.ARRTRIM a $ 1 1 "1" -127.0.0.1:6379> JSON.GET a +127.0.0.1:7379> JSON.GET a "[1]" -127.0.0.1:6379> +127.0.0.1:7379> ``` ### Trimming array with out of bound index Trimming an array with out-of-bounds indices -```plaintext -127.0.0.1:6379> JSON.SET c $ '[1, 2, 3, 4, 5]' +```bash +127.0.0.1:7379> JSON.SET c $ '[1, 2, 3, 4, 5]' "OK" -127.0.0.1:6379> JSON.ARRTRIM c $ -10 10 +127.0.0.1:7379> JSON.ARRTRIM c $ -10 10 1) "5" -127.0.0.1:6379> JSON.GET c +127.0.0.1:7379> JSON.GET c "[1,2,3,4,5]" ``` @@ -113,10 +113,10 @@ Trimming an array with out-of-bounds indices Error when path does not exist -```plaintext -127.0.0.1:6379> JSON.SET d $ '[1, 2, 3, 4, 5]' +```bash +127.0.0.1:7379> JSON.SET d $ '[1, 2, 3, 4, 5]' "OK" -127.0.0.1:6379> JSON.ARRTRIM d . -10 10 +127.0.0.1:7379> JSON.ARRTRIM d . -10 10 (error) ERROR Path '.' does not exist ``` @@ -124,10 +124,10 @@ Error when path does not exist Error when key does not exist -```plaintext -127.0.0.1:6379> JSON.SET a $ '[1, 2, 3, 4, 5]' +```bash +127.0.0.1:7379> JSON.SET a $ '[1, 2, 3, 4, 5]' "OK" -127.0.0.1:6379> JSON.ARRTRIM aa . -10 10 +127.0.0.1:7379> JSON.ARRTRIM aa . -10 10 (error) ERROR key does not exist ``` diff --git a/docs/src/content/docs/commands/JSON.CLEAR.md b/docs/src/content/docs/commands/JSON.CLEAR.md index a3c46e41f..c34653656 100644 --- a/docs/src/content/docs/commands/JSON.CLEAR.md +++ b/docs/src/content/docs/commands/JSON.CLEAR.md @@ -7,7 +7,7 @@ The `JSON.CLEAR` command allows you to manipulate JSON data stored in DiceDB. Th ## Syntax -``` +```bash JSON.CLEAR key [path] ``` diff --git a/docs/src/content/docs/commands/JSON.DEBUG.md b/docs/src/content/docs/commands/JSON.DEBUG.md index 17fc47049..22a2c5e1b 100644 --- a/docs/src/content/docs/commands/JSON.DEBUG.md +++ b/docs/src/content/docs/commands/JSON.DEBUG.md @@ -9,7 +9,7 @@ The `JSON.DEBUG` command in DiceDB is part of the DiceDBJSON module, which allow ### Syntax -``` +```bash JSON.DEBUG [path] ``` @@ -57,8 +57,8 @@ The `JSON.DEBUG` command can raise errors in the following scenarios: ### Example 1: Debugging Memory Usage of Entire JSON Data -```sh -127.0.0.1:6379> JSON.DEBUG MEMORY myjson +```bash +127.0.0.1:7379> JSON.DEBUG MEMORY myjson (integer) 256 ``` @@ -66,8 +66,8 @@ In this example, the `JSON.DEBUG MEMORY` command is used to get the memory usage ### Example 2: Debugging Memory Usage of a Specific Path -```sh -127.0.0.1:6379> JSON.DEBUG MEMORY myjson $.store.book[0] +```bash +127.0.0.1:7379> JSON.DEBUG MEMORY myjson $.store.book[0] (integer) 64 ``` @@ -75,8 +75,8 @@ In this example, the `JSON.DEBUG MEMORY` command is used to get the memory usage ### Example 3: Handling Non-Existent Key -```sh -127.0.0.1:6379> JSON.DEBUG MEMORY nonExistentKey +```bash +127.0.0.1:7379> JSON.DEBUG MEMORY nonExistentKey (error) ERR no such key ``` @@ -84,8 +84,8 @@ In this example, the `JSON.DEBUG MEMORY` command is used on a non-existent key ` ### Example 4: Handling Invalid Path -```sh -127.0.0.1:6379> JSON.DEBUG MEMORY myjson $.nonExistentPath +```bash +127.0.0.1:7379> JSON.DEBUG MEMORY myjson $.nonExistentPath (error) ERR path '$.nonExistentPath' does not exist ``` diff --git a/docs/src/content/docs/commands/JSON.DEL.md b/docs/src/content/docs/commands/JSON.DEL.md index b0badcefb..83344d7b2 100644 --- a/docs/src/content/docs/commands/JSON.DEL.md +++ b/docs/src/content/docs/commands/JSON.DEL.md @@ -7,7 +7,7 @@ The `JSON.DEL` command is part of the DiceDBJSON module, which allows you to man ## Syntax -``` +```bash JSON.DEL key [path] ``` @@ -48,41 +48,41 @@ The `JSON.DEL` command can raise the following errors: ### Deleting an Entire JSON Document -```shell -127.0.0.1:6379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' +```bash +127.0.0.1:7379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' OK -127.0.0.1:6379> JSON.DEL myjson +127.0.0.1:7379> JSON.DEL myjson (integer) 1 -127.0.0.1:6379> JSON.GET myjson +127.0.0.1:7379> JSON.GET myjson (nil) ``` ### Deleting a Specific Path -```shell -127.0.0.1:6379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' +```bash +127.0.0.1:7379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' OK -127.0.0.1:6379> JSON.DEL myjson $.age +127.0.0.1:7379> JSON.DEL myjson $.age (integer) 1 -127.0.0.1:6379> JSON.GET myjson +127.0.0.1:7379> JSON.GET myjson "{\"name\":\"John\",\"city\":\"New York\"}" ``` ### Deleting a Non-Existent Path -```shell -127.0.0.1:6379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' +```bash +127.0.0.1:7379> JSON.SET myjson $ '{"name": "John", "age": 30, "city": "New York"}' OK -127.0.0.1:6379> JSON.DEL myjson $.address +127.0.0.1:7379> JSON.DEL myjson $.address (integer) 0 ``` ### Error: Key Does Not Hold a JSON Document -```shell -127.0.0.1:6379> SET mystring "Hello, World!" +```bash +127.0.0.1:7379> SET mystring "Hello, World!" OK -127.0.0.1:6379> JSON.DEL mystring +127.0.0.1:7379> JSON.DEL mystring (error) ERROR Existing key has wrong Dice type ``` diff --git a/docs/src/content/docs/commands/JSON.FORGET.md b/docs/src/content/docs/commands/JSON.FORGET.md index 6e77463f4..77f1b4b46 100644 --- a/docs/src/content/docs/commands/JSON.FORGET.md +++ b/docs/src/content/docs/commands/JSON.FORGET.md @@ -7,7 +7,7 @@ The `JSON.FORGET` command in DiceDB is used to delete a specified path from a JS ## Syntax -``` +```bash JSON.FORGET key path ``` diff --git a/docs/src/content/docs/commands/JSON.GET.md b/docs/src/content/docs/commands/JSON.GET.md index 611b2670c..ef60d33b4 100644 --- a/docs/src/content/docs/commands/JSON.GET.md +++ b/docs/src/content/docs/commands/JSON.GET.md @@ -7,7 +7,7 @@ The `JSON.GET` command allows you to store, update, and retrieve JSON values in ## Syntax -``` +```bash JSON.GET [path] ``` diff --git a/docs/src/content/docs/commands/JSON.MSET.md b/docs/src/content/docs/commands/JSON.MSET.md index e8ed00748..fafc588ae 100644 --- a/docs/src/content/docs/commands/JSON.MSET.md +++ b/docs/src/content/docs/commands/JSON.MSET.md @@ -17,7 +17,7 @@ The `JSON.MSET` command requires an even number of arguments. The arguments are ### Example -```plaintext +```bash JSON.MSET key1 json1 key2 json2 ... keyN jsonN ``` @@ -56,8 +56,8 @@ The `JSON.MSET` command can raise errors in the following scenarios: ### Setting Multiple JSON Values -```plaintext -127.0.0.1:6379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 '{"name": "Bob", "age": 25}' +```bash +127.0.0.1:7379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 '{"name": "Bob", "age": 25}' OK ``` @@ -65,8 +65,8 @@ In this example, two keys (`user:1` and `user:2`) are set with their respective ### Error Example: Odd Number of Arguments -```plaintext -127.0.0.1:6379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 +```bash +127.0.0.1:7379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 (error) ERR wrong number of arguments for 'JSON.MSET' command ``` @@ -74,8 +74,8 @@ In this example, the command fails because the number of arguments is odd. DiceD ### Error Example: Invalid JSON -```plaintext -127.0.0.1:6379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 '{name: "Bob", age: 25}' +```bash +127.0.0.1:7379> JSON.MSET user:1 '{"name": "Alice", "age": 30}' user:2 '{name: "Bob", age: 25}' (error) ERR invalid JSON string ``` diff --git a/docs/src/content/docs/commands/JSON.NUMINCRBY.md b/docs/src/content/docs/commands/JSON.NUMINCRBY.md index e74b18153..0c5118b94 100644 --- a/docs/src/content/docs/commands/JSON.NUMINCRBY.md +++ b/docs/src/content/docs/commands/JSON.NUMINCRBY.md @@ -7,7 +7,7 @@ The `JSON.NUMINCRBY` command is part of the DiceDBJSON module, which allows for ## Syntax -```plaintext +```bash JSON.NUMINCRBY ``` @@ -47,7 +47,7 @@ JSON.NUMINCRBY Create a document: -```shell +```bash 127.0.0.1:7379> JSON.SET user:1001 $ '{"name": "John Doe", "age": 30, "balance": 100.50, "account": {"id": 0, "lien": 0, "balance": 100.50}}' "OK" ``` @@ -56,7 +56,7 @@ Create a document: Incrementing the value of `age` object by 1 -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 $.age 1 "[31]" ``` @@ -65,7 +65,7 @@ Incrementing the value of `age` object by 1 Incrementing the value of `balance` object by 25.75 -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 $.balance 25.75 "[126.25]" ``` @@ -74,7 +74,7 @@ Incrementing the value of `balance` object by 25.75 Finding and recursively incrementing the values of both `balance` objects by 25.75 -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 $..balance 25.75 "[126.25,126.25]" ``` @@ -83,7 +83,7 @@ Finding and recursively incrementing the values of both `balance` objects by 25. Incrementing a non-numeric value -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 $.name 5 "[null]" ``` @@ -92,7 +92,7 @@ Incrementing a non-numeric value Incrementing a non-existent path -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 $.nonexistent 10 "[]" ``` @@ -101,7 +101,7 @@ Incrementing a non-existent path Trying to increment an invalid path will result in an error -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1001 . 5 (error) ERROR invalid JSONPath ``` @@ -110,7 +110,7 @@ Trying to increment an invalid path will result in an error Trying to increment a path for a non-existent key will result in an error -```shell +```bash 127.0.0.1:7379> JSON.NUMINCRBY user:1002 . 5 (error) ERROR could not perform this operation on a key that doesn't exist ``` diff --git a/docs/src/content/docs/commands/JSON.OBJKEYS.md b/docs/src/content/docs/commands/JSON.OBJKEYS.md index c0c8a2383..88b2f37e6 100644 --- a/docs/src/content/docs/commands/JSON.OBJKEYS.md +++ b/docs/src/content/docs/commands/JSON.OBJKEYS.md @@ -67,10 +67,10 @@ JSON.OBJKEYS key [path] Retrieving Keys of the Root Object -```plaintext -127.0.0.1:6379> JSON.SET a $ '{"name": "Alice", "age": 30, "address": {"city": "Wonderland", "zipcode": "12345"}}' +```bash +127.0.0.1:7379> JSON.SET a $ '{"name": "Alice", "age": 30, "address": {"city": "Wonderland", "zipcode": "12345"}}' "OK" -127.0.0.1:6379> JSON.OBJKEYS a $ +127.0.0.1:7379> JSON.OBJKEYS a $ 1) "name" 2) "age" 3) "address" @@ -80,10 +80,10 @@ Retrieving Keys of the Root Object Retrieving Keys of a Nested Object -```plaintext -127.0.0.1:6379> JSON.SET b $ '{"name": "Alice", "partner": {"name": "Bob", "age": 28}}' +```bash +127.0.0.1:7379> JSON.SET b $ '{"name": "Alice", "partner": {"name": "Bob", "age": 28}}' "OK" -127.0.0.1:6379> JSON.OBJKEYS b $.partner +127.0.0.1:7379> JSON.OBJKEYS b $.partner 1) "name" 2) "age" ``` @@ -92,10 +92,10 @@ Retrieving Keys of a Nested Object Error When Path Points to a Non-Object Type -```plaintext -127.0.0.1:6379> JSON.SET c $ '{"name": "Alice", "age": 30}' +```bash +127.0.0.1:7379> JSON.SET c $ '{"name": "Alice", "age": 30}' "OK" -127.0.0.1:6379> JSON.OBJKEYS c $.age +127.0.0.1:7379> JSON.OBJKEYS c $.age (nil) ``` @@ -103,10 +103,10 @@ Error When Path Points to a Non-Object Type Error When Path Does Not Exist -```plaintext -127.0.0.1:6379> JSON.SET d $ '{"name": "Alice", "address": {"city": "Wonderland"}}' +```bash +127.0.0.1:7379> JSON.SET d $ '{"name": "Alice", "address": {"city": "Wonderland"}}' "OK" -127.0.0.1:6379> JSON.OBJKEYS d $.nonexistentPath +127.0.0.1:7379> JSON.OBJKEYS d $.nonexistentPath (empty list or set) ``` @@ -114,8 +114,8 @@ Error When Path Does Not Exist Error When Key Does Not Exist -```plaintext -127.0.0.1:6379> JSON.OBJKEYS nonexistent_key $ +```bash +127.0.0.1:7379> JSON.OBJKEYS nonexistent_key $ (error) ERROR could not perform this operation on a key that doesn't exist ``` diff --git a/docs/src/content/docs/commands/JSON.STRLEN.md b/docs/src/content/docs/commands/JSON.STRLEN.md index 292abf329..cf9d58fb2 100644 --- a/docs/src/content/docs/commands/JSON.STRLEN.md +++ b/docs/src/content/docs/commands/JSON.STRLEN.md @@ -7,7 +7,7 @@ The `JSON.STRLEN` command is used to determine the length of a JSON string at a ## Syntax -``` +```bash JSON.STRLEN ``` diff --git a/docs/src/content/docs/commands/JSON.TOGGLE.md b/docs/src/content/docs/commands/JSON.TOGGLE.md index beebdf557..45283181c 100644 --- a/docs/src/content/docs/commands/JSON.TOGGLE.md +++ b/docs/src/content/docs/commands/JSON.TOGGLE.md @@ -76,13 +76,13 @@ The `JSON.TOGGLE` command can raise the following errors: #### Command -```shell +```bash JSON.TOGGLE user:1001 $.active ``` #### Result -```shell +```bash (integer) 1 ``` @@ -102,13 +102,13 @@ JSON.TOGGLE user:1001 $.active #### Command -```shell +```bash JSON.TOGGLE user:1001 $.settings.notifications ``` #### Result -```shell +```bash (integer) 1 ``` @@ -128,13 +128,13 @@ JSON.TOGGLE user:1001 $.settings.notifications #### Command -```shell +```bash JSON.TOGGLE user:1001 $.nonexistent ``` #### Result -```shell +```bash (integer) 0 ``` @@ -142,12 +142,12 @@ JSON.TOGGLE user:1001 $.nonexistent #### Command -```shell +```bash JSON.TOGGLE user:1001 $.name ``` #### Result -```shell +```bash (error) ERR value at path is not a boolean ``` diff --git a/docs/src/content/docs/commands/JSON.TYPE.md b/docs/src/content/docs/commands/JSON.TYPE.md index 5e7b774be..467d97be2 100644 --- a/docs/src/content/docs/commands/JSON.TYPE.md +++ b/docs/src/content/docs/commands/JSON.TYPE.md @@ -7,7 +7,7 @@ The `JSON.TYPE` command allows you to work with JSON data structures in DiceDB. ## Syntax -``` +```bash JSON.TYPE ``` diff --git a/docs/src/content/docs/commands/KEYS.md b/docs/src/content/docs/commands/KEYS.md index 5bf6eeb44..f35772f02 100644 --- a/docs/src/content/docs/commands/KEYS.md +++ b/docs/src/content/docs/commands/KEYS.md @@ -7,7 +7,7 @@ The `KEYS` command in DiceDB is used to find all keys matching a given pattern. ## Syntax -```plaintext +```bash KEYS pattern ``` @@ -37,7 +37,7 @@ Use `\` to escape special characters if you want to match them verbatim. ### Basic example -```plaintext +```bash 127.0.0.1:7379> SET key1 "value1" OK 127.0.0.1:7379> SET key2 "value2" @@ -51,7 +51,7 @@ OK ### Using wildcards -```plaintext +```bash 127.0.0.1:7379> SET key1 "value1" OK 127.0.0.1:7379> SET key2 "value2" @@ -66,7 +66,7 @@ OK ### Using character ranges -```plaintext +```bash 127.0.0.1:7379> SET key1 "value1" OK 127.0.0.1:7379> SET key2 "value2" @@ -80,7 +80,7 @@ OK ### Using \ to escape special characters -```plaintext +```bash 127.0.0.1:7379> SET key1 "value1" OK 127.0.0.1:7379> SET key2 "value2" @@ -122,7 +122,7 @@ The `KEYS` command is straightforward and does not have many error conditions. H - `SCAN`: The `SCAN` command is a cursor-based iterator that allows you to incrementally iterate over the keyspace without blocking the server. It is a more efficient alternative to `KEYS` for large datasets. -```plaintext +```bash 127.0.0.1:7379> SCAN 0 MATCH key* 1) "0" 2) 1) "key1" diff --git a/docs/src/content/docs/commands/LATENCY.md b/docs/src/content/docs/commands/LATENCY.md index 985dac747..78cbab6ea 100644 --- a/docs/src/content/docs/commands/LATENCY.md +++ b/docs/src/content/docs/commands/LATENCY.md @@ -8,7 +8,7 @@ The `LATENCY` command in DiceDB is used to measure and analyze the latency of va ## Command Syntax -```plaintext +```bash LATENCY [SUBCOMMAND] [ARGUMENTS] ``` diff --git a/docs/src/content/docs/commands/LLEN.md b/docs/src/content/docs/commands/LLEN.md index 2d2cbce34..f779a1097 100644 --- a/docs/src/content/docs/commands/LLEN.md +++ b/docs/src/content/docs/commands/LLEN.md @@ -7,7 +7,7 @@ The `LLEN` command in DiceDB is used to obtain the length of a list stored at a ## Syntax -``` +```bash LLEN key ``` diff --git a/docs/src/content/docs/commands/LPOP.md b/docs/src/content/docs/commands/LPOP.md index 055fa5c76..7d8783178 100644 --- a/docs/src/content/docs/commands/LPOP.md +++ b/docs/src/content/docs/commands/LPOP.md @@ -7,7 +7,7 @@ The `LPOP` command in DiceDB removes and returns the first element of a list at ## Syntax -```plaintext +```bash LPOP key ``` diff --git a/docs/src/content/docs/commands/LPUSH.md b/docs/src/content/docs/commands/LPUSH.md index 6592e2e2f..e949f21e8 100644 --- a/docs/src/content/docs/commands/LPUSH.md +++ b/docs/src/content/docs/commands/LPUSH.md @@ -7,7 +7,7 @@ The `LPUSH` command is used to insert one or multiple values at the head (left) ## Syntax -``` +```bash LPUSH key value [value ...] ``` @@ -50,7 +50,7 @@ LPUSH key value [value ...] Insert the value `world` at the head of the list stored at key `mylist`. If `mylist` does not exist, a new list is created. -```shell +```bash 127.0.0.1:7379> LPUSH mylist "world" (integer) 1 ``` @@ -60,7 +60,7 @@ Insert the value `world` at the head of the list stored at key `mylist`. If `myl Insert the value `hello` and `world` at the head of the list stored at key `mylist`. After execution, `world` will be the first element, followed by `hello`. -```shell +```bash 127.0.0.1:7379> LPUSH mylist "hello" "world" (integer) 2 ``` @@ -69,7 +69,7 @@ Insert the value `hello` and `world` at the head of the list stored at key `myli Create a new list with the key `newlist` and inserts the value `first` at the head. -```shell +```bash 127.0.0.1:7379> LPUSH newlist "first" (integer) 1 ``` @@ -78,7 +78,7 @@ Create a new list with the key `newlist` and inserts the value `first` at the he Insert the value `value` at the head of the key `mystring`, which stores a string, not a list. -```shell +```bash 127.0.0.1:7379> SET mystring "not a list" OK 127.0.0.1:7379> LPUSH mystring "value" diff --git a/docs/src/content/docs/commands/MGET.md b/docs/src/content/docs/commands/MGET.md index 132ecded7..0b17524fb 100644 --- a/docs/src/content/docs/commands/MGET.md +++ b/docs/src/content/docs/commands/MGET.md @@ -7,7 +7,7 @@ The `MGET` command in DiceDB is used to retrieve the values of multiple keys in ## Syntax -``` +```bash MGET key [key ...] ``` diff --git a/docs/src/content/docs/commands/MSET.md b/docs/src/content/docs/commands/MSET.md index 01730929a..633e52b4d 100644 --- a/docs/src/content/docs/commands/MSET.md +++ b/docs/src/content/docs/commands/MSET.md @@ -7,7 +7,7 @@ The `MSET` command in DiceDB is used to set multiple key-value pairs in a single ## Syntax -```plaintext +```bash MSET key1 value1 [key2 value2 ...] ``` @@ -76,7 +76,7 @@ OK Attempting to set an odd number of arguments: -```sh +```bash 127.0.0.1:7379> MSET key1 "value1" key2 (error) ERROR wrong number of arguments for 'mset' command ``` \ No newline at end of file diff --git a/docs/src/content/docs/commands/OBJECT.md b/docs/src/content/docs/commands/OBJECT.md index c14378907..668aed902 100644 --- a/docs/src/content/docs/commands/OBJECT.md +++ b/docs/src/content/docs/commands/OBJECT.md @@ -7,7 +7,7 @@ The `OBJECT` command in DiceDB is used to inspect the internals of DiceDB object ## Syntax -```plaintext +```bash OBJECT ``` @@ -54,13 +54,13 @@ The `OBJECT` command can raise errors in the following scenarios: ### Example 1: Using the `REFCOUNT` Subcommand -```plaintext +```bash OBJECT REFCOUNT mykey ``` `Response:` -```plaintext +```bash (integer) 1 ``` @@ -68,13 +68,13 @@ This response indicates that the value associated with `mykey` has a reference c ### Example 2: Using the `ENCODING` Subcommand -```plaintext +```bash OBJECT ENCODING mykey ``` `Response:` -```plaintext +```bash "embstr" ``` @@ -82,13 +82,13 @@ This response indicates that the value associated with `mykey` is stored using t ### Example 3: Using the `IDLETIME` Subcommand -```plaintext +```bash OBJECT IDLETIME mykey ``` `Response:` -```plaintext +```bash (integer) 120 ``` @@ -96,13 +96,13 @@ This response indicates that `mykey` has been idle for 120 seconds. ### Example 4: Using the `FREQ` Subcommand -```plaintext +```bash OBJECT FREQ mykey ``` `Response:` -```plaintext +```bash (integer) 5 ``` diff --git a/docs/src/content/docs/commands/PERSIST.md b/docs/src/content/docs/commands/PERSIST.md index 747df3a5a..29fe4d2b7 100644 --- a/docs/src/content/docs/commands/PERSIST.md +++ b/docs/src/content/docs/commands/PERSIST.md @@ -7,7 +7,7 @@ The `PERSIST` command is used to remove the expiration from a key in DiceDB. If ## Syntax -``` +```bash PERSIST key ``` diff --git a/docs/src/content/docs/commands/PFADD.md b/docs/src/content/docs/commands/PFADD.md index d7cf05250..24c58db52 100644 --- a/docs/src/content/docs/commands/PFADD.md +++ b/docs/src/content/docs/commands/PFADD.md @@ -7,7 +7,7 @@ The `PFADD` command in DiceDB is used to add elements to a HyperLogLog data stru ## Syntax -``` +```bash PFADD key element [element ...] ``` diff --git a/docs/src/content/docs/commands/PFCOUNT.md b/docs/src/content/docs/commands/PFCOUNT.md index ec244ed30..c38cd2de1 100644 --- a/docs/src/content/docs/commands/PFCOUNT.md +++ b/docs/src/content/docs/commands/PFCOUNT.md @@ -7,7 +7,7 @@ The `PFCOUNT` command in DiceDB is used to return the approximate cardinality (i ## Syntax -``` +```bash PFCOUNT key [key ...] ``` diff --git a/docs/src/content/docs/commands/PFMERGE.md b/docs/src/content/docs/commands/PFMERGE.md index 6fb5ede26..8b8983e67 100644 --- a/docs/src/content/docs/commands/PFMERGE.md +++ b/docs/src/content/docs/commands/PFMERGE.md @@ -7,7 +7,7 @@ The `PFMERGE` command in DiceDB is used to merge multiple HyperLogLog data struc ## Syntax -``` +```bash PFMERGE destkey sourcekey [sourcekey ...] ``` @@ -52,7 +52,7 @@ The `PFMERGE` command can raise errors in the following scenarios: Suppose you have three HyperLogLogs stored at keys `hll1`, `hll2`, and `hll3`, and you want to merge them into a new HyperLogLog stored at key `hll_merged`. -```sh +```bash 127.0.0.1:7379> PFADD hll1 "a" "b" "c" (integer) 1 127.0.0.1:7379> PFADD hll2 "c" "d" "e" @@ -69,7 +69,7 @@ OK If the `destkey` already exists, it will be overwritten by the merged HyperLogLog. -```sh +```bash 127.0.0.1:7379> PFADD hll_merged "x" "y" "z" (integer) 1 127.0.0.1:7379> PFMERGE hll_merged hll1 hll2 hll3 @@ -82,7 +82,7 @@ OK If a `sourcekey` does not exist, DiceDB will treat it as an empty HyperLogLog. -```sh +```bash 127.0.0.1:7379> PFMERGE hll_merged hll1 hll2 non_existent_key OK 127.0.0.1:7379> PFCOUNT hll_merged @@ -93,7 +93,7 @@ OK if a `sourcekey` exists and is not of type HyperLogLog, the command will result in an error -```sh +```bash 127.0.0.1:7379> PFMERGE hll_merged not_hyperLogLog (error) WRONGTYPE Key is not a valid HyperLogLog string value diff --git a/docs/src/content/docs/commands/PING.md b/docs/src/content/docs/commands/PING.md index a2dd12253..8ec596913 100644 --- a/docs/src/content/docs/commands/PING.md +++ b/docs/src/content/docs/commands/PING.md @@ -9,7 +9,7 @@ The `PING` command in DiceDB is used to test the connection between the client a The basic syntax of the `PING` command is as follows: -``` +```bash PING [message] ``` diff --git a/docs/src/content/docs/commands/PTTL.md b/docs/src/content/docs/commands/PTTL.md index dbee30529..fffe3dacc 100644 --- a/docs/src/content/docs/commands/PTTL.md +++ b/docs/src/content/docs/commands/PTTL.md @@ -7,7 +7,7 @@ The `PTTL` command in DiceDB is used to retrieve the remaining time to live (TTL ## Syntax -``` +```bash PTTL key ``` diff --git a/docs/src/content/docs/commands/RENAME.md b/docs/src/content/docs/commands/RENAME.md index 206d152b0..39d8aabb7 100644 --- a/docs/src/content/docs/commands/RENAME.md +++ b/docs/src/content/docs/commands/RENAME.md @@ -7,7 +7,7 @@ The `RENAME` command in DiceDB is used to change the name of an existing key to ## Syntax -```plaintext +```bash RENAME oldkey newkey ``` diff --git a/docs/src/content/docs/commands/RPOP.md b/docs/src/content/docs/commands/RPOP.md index 7f464d78f..b5fdaa930 100644 --- a/docs/src/content/docs/commands/RPOP.md +++ b/docs/src/content/docs/commands/RPOP.md @@ -7,7 +7,7 @@ The `RPOP` command in DiceDB is used to remove and return the last element of a ## Syntax -``` +```bash RPOP key ``` diff --git a/docs/src/content/docs/commands/RPUSH.md b/docs/src/content/docs/commands/RPUSH.md index d7fa6bca1..be67c970f 100644 --- a/docs/src/content/docs/commands/RPUSH.md +++ b/docs/src/content/docs/commands/RPUSH.md @@ -7,7 +7,7 @@ The `RPUSH` command is used in DiceDB to insert one or multiple values at the ta ## Syntax -``` +```bash RPUSH key value [value ...] ``` diff --git a/docs/src/content/docs/commands/SADD.md b/docs/src/content/docs/commands/SADD.md index b7e562325..efb043312 100644 --- a/docs/src/content/docs/commands/SADD.md +++ b/docs/src/content/docs/commands/SADD.md @@ -7,7 +7,7 @@ The `SADD` command in DiceDB is used to add one or more members to a set. If the ## Syntax -``` +```bash SADD key member [member ...] ``` diff --git a/docs/src/content/docs/commands/SCARD.md b/docs/src/content/docs/commands/SCARD.md index 7fcedd932..763995e94 100644 --- a/docs/src/content/docs/commands/SCARD.md +++ b/docs/src/content/docs/commands/SCARD.md @@ -7,7 +7,7 @@ The `SCARD` command in DiceDB is used to get the number of members in a set. Thi ## Syntax -``` +```bash SCARD key ``` diff --git a/docs/src/content/docs/commands/SDIFF.md b/docs/src/content/docs/commands/SDIFF.md index 634209496..cca5b1da5 100644 --- a/docs/src/content/docs/commands/SDIFF.md +++ b/docs/src/content/docs/commands/SDIFF.md @@ -7,7 +7,7 @@ The `SDIFF` command in DiceDB is used to compute the difference between multiple ## Syntax -``` +```bash SDIFF key1 [key2 ... keyN] ``` diff --git a/docs/src/content/docs/commands/SELECT.md b/docs/src/content/docs/commands/SELECT.md index 69d4409c3..18ca810ed 100644 --- a/docs/src/content/docs/commands/SELECT.md +++ b/docs/src/content/docs/commands/SELECT.md @@ -3,13 +3,13 @@ title: SELECT description: Documentation for the DiceDB command SELECT --- -**Note:** As of today, DiceDB does not support multiple databases. Therefore, the `SELECT` command is currently a dummy method and does not affect the database. It remains as a placeholder. +> **Important Note:** As of the current version, DiceDB does not support multiple databases. Therefore, the `SELECT` command is currently a dummy method and does not affect the database. It remains as a placeholder. The `SELECT` command is used to switch the currently selected database for the current connection in DiceDB. By default, DiceDB starts with database 0, but it supports multiple databases, which can be accessed by using the `SELECT` command. This command is essential for managing data across different logical databases within a single DiceDB instance. ## Syntax -``` +```bash SELECT index ``` @@ -39,48 +39,45 @@ When the `SELECT` command is issued, the current connection's context is switche 1. `Invalid Database Index`: - - `Error`: `(error) ERR DB index is out of range` - - `Condition`: If the specified database index is outside the range of available databases. + - Error Message: `(error) ERR DB index is out of range` + - Occurs when the specified database index exceeds the configured maximum (default 15) 2. `Non-Integer Index`: - - - `Error`: `(error) ERR value is not an integer or out of range` - - `Condition`: If the provided index is not a valid integer. + - Error Message: `(error) ERR value is not an integer or out of range` + - Occurs when the provided index is not a valid integer ## Example Usage ### Switching to Database 1 -```shell +```bash 127.0.0.1:7379> SELECT 1 OK ``` -In this example, the connection switches to database 1. All subsequent commands will operate on database 1. - -### Switching to Database 0 +### Switching Back to Default Database -```shell +```bash 127.0.0.1:7379> SELECT 0 OK ``` -Here, the connection switches back to the default database 0. +### Invalid Database Index ### Error Example: Invalid Database Index -```shell +```bash 127.0.0.1:7379> SELECT 16 (error) ERR DB index is out of range ``` -In this example, an error is raised because the specified database index 16 is outside the default range of 0-15. +### Invalid Input Type -### Error Example: Non-Integer Index - -```shell +```bash 127.0.0.1:7379> SELECT one (error) ERR value is not an integer or out of range ``` -In this example, an error is raised because the provided index is not a valid integer. +## Notes + +As mentioned at the beginning of this document, the current version of DiceDB does not support multiple databases. The `SELECT` command is implemented as a placeholder for future functionality. All operations, regardless of the SELECT command, will continue to operate on a single database space. \ No newline at end of file diff --git a/docs/src/content/docs/commands/SET.md b/docs/src/content/docs/commands/SET.md index 14b076641..518940b78 100644 --- a/docs/src/content/docs/commands/SET.md +++ b/docs/src/content/docs/commands/SET.md @@ -7,7 +7,7 @@ The `SET` command in DiceDB is used to set the value of a key. If the key alread ## Syntax -``` +```bash SET key value [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL] [NX | XX] ``` diff --git a/docs/src/content/docs/commands/SETBIT.md b/docs/src/content/docs/commands/SETBIT.md index b77f6dfad..f4cb272ef 100644 --- a/docs/src/content/docs/commands/SETBIT.md +++ b/docs/src/content/docs/commands/SETBIT.md @@ -7,7 +7,7 @@ The `SETBIT` command in DiceDB is used to set or clear the bit at a specified of ## Syntax -``` +```bash SETBIT key offset value ``` @@ -37,7 +37,7 @@ The command returns the original bit value stored at the specified offset before ### Setting a Bit -```DiceDB +```bash SETBIT mykey 7 1 ``` @@ -45,7 +45,7 @@ This command sets the bit at offset 7 in the string value stored at `mykey` to 1 ### Clearing a Bit -```DiceDB +```bash SETBIT mykey 7 0 ``` @@ -53,7 +53,7 @@ This command clears the bit at offset 7 in the string value stored at `mykey` to ### Checking the Original Bit Value -```DiceDB +```bash SETBIT mykey 7 1 ``` @@ -61,7 +61,7 @@ If the bit at offset 7 was previously 0, this command will return 0 and then set ### Extending the String -```DiceDB +```bash SETBIT mykey 100 1 ``` @@ -71,7 +71,7 @@ If the string stored at `mykey` is shorter than 101 bits, it will be extended, a ### Invalid Offset -```DiceDB +```bash SETBIT mykey -1 1 ``` @@ -79,7 +79,7 @@ This command will raise an error: `ERR bit is not an integer or out of range` be ### Invalid Value -```DiceDB +```bash SETBIT mykey 7 2 ``` @@ -87,7 +87,7 @@ This command will raise an error: `ERR bit is not an integer or out of range` be ### Wrong Type -```DiceDB +```bash SET mykey "Hello" SETBIT mykey 7 1 ``` diff --git a/docs/src/content/docs/commands/SINTER.md b/docs/src/content/docs/commands/SINTER.md index 46a4f3e78..81b9ca00a 100644 --- a/docs/src/content/docs/commands/SINTER.md +++ b/docs/src/content/docs/commands/SINTER.md @@ -7,7 +7,7 @@ The `SINTER` command in DiceDB is used to compute the intersection of multiple s ## Syntax -``` +```bash SINTER key [key ...] ``` ## Parameters @@ -45,7 +45,7 @@ If any of the specified keys do not exist, they are treated as empty sets. The i ### Example 1: Basic Intersection -```shell +```bash # Add elements to sets 127.0.0.1:7379> SADD set1 "a" "b" "c" (integer) 3 @@ -61,7 +61,7 @@ If any of the specified keys do not exist, they are treated as empty sets. The i ### Example 2: Intersection with Non-Existent Set -```shell +```bash # Add elements to sets 127.0.0.1:7379> SADD set1 "a" "b" "c" (integer) 3 @@ -76,7 +76,7 @@ Note: By default, non-existent keys (such as set3 in the example above) are trea ### Example 3: Error Handling - Wrong Type -```shell +```bash # Add elements to sets 127.0.0.1:7379> SADD set1 "a" "b" "c" (integer) 3 @@ -91,7 +91,7 @@ SINTER set1 stringKey ### Example 4: Error Handling - No Keys Provided -```shell +```bash # Attempt to compute intersection without providing any keys 127.0.0.1:7379> SINTER (error) ERR wrong number of arguments for 'sinter' command diff --git a/docs/src/content/docs/commands/SREM.md b/docs/src/content/docs/commands/SREM.md index 21bf7dcd6..63e987e4c 100644 --- a/docs/src/content/docs/commands/SREM.md +++ b/docs/src/content/docs/commands/SREM.md @@ -7,7 +7,7 @@ The `SREM` command is used to remove one or more members from a set stored at a ## Syntax -``` +```bash SREM key member [member ...] ``` @@ -39,7 +39,7 @@ When the `SREM` command is executed, the following steps occur: ### Example 1: Removing a single member from a set -```DiceDB +```bash SADD myset "one" "two" "three" SREM myset "two" ``` @@ -54,7 +54,7 @@ SREM myset "two" ### Example 2: Removing multiple members from a set -```DiceDB +```bash SADD myset "one" "two" "three" SREM myset "two" "three" ``` @@ -69,7 +69,7 @@ SREM myset "two" "three" ### Example 3: Removing a non-existing member from a set -```DiceDB +```bash SADD myset "one" "two" "three" SREM myset "four" ``` @@ -84,7 +84,7 @@ SREM myset "four" ### Example 4: Removing members from a non-existing set -```DiceDB +```bash SREM myset "one" ``` @@ -98,7 +98,7 @@ SREM myset "one" ### Example 5: Error when key is not a set -```DiceDB +```bash SET mykey "value" SREM mykey "one" ``` diff --git a/docs/src/content/docs/commands/TOUCH.md b/docs/src/content/docs/commands/TOUCH.md index 0e7d531fa..f22b266aa 100644 --- a/docs/src/content/docs/commands/TOUCH.md +++ b/docs/src/content/docs/commands/TOUCH.md @@ -7,7 +7,7 @@ The `TOUCH` command in DiceDB is used to update the last access time of one or m ## Syntax -```plaintext +```bash TOUCH key [key ...] ``` @@ -43,7 +43,7 @@ The `TOUCH` command can raise the following errors: ### Single Key -```plaintext +```bash SET mykey "Hello" TOUCH mykey ``` @@ -52,7 +52,7 @@ In this example, the `TOUCH` command updates the last access time of the key `my ### Multiple Keys -```plaintext +```bash SET key1 "value1" SET key2 "value2" TOUCH key1 key2 key3 @@ -64,7 +64,7 @@ In this example, the `TOUCH` command attempts to update the last access time for Trying to touch key `mylist` will result in a `WRONGTYPE` error because `mylist` is a list, not a string. -```plaintext +```bash LPUSH mylist "element" TOUCH mylist ``` diff --git a/docs/src/content/docs/commands/ZCARD.md b/docs/src/content/docs/commands/ZCARD.md index 5f2f086db..77bba2625 100644 --- a/docs/src/content/docs/commands/ZCARD.md +++ b/docs/src/content/docs/commands/ZCARD.md @@ -7,7 +7,7 @@ The `ZCARD` command in DiceDB is used to obtain the cardinality (number of eleme ## Syntax -``` +```bash ZCARD key ``` diff --git a/docs/src/content/docs/commands/ZCOUNT.md b/docs/src/content/docs/commands/ZCOUNT.md index b9cff8f13..4456aee9a 100644 --- a/docs/src/content/docs/commands/ZCOUNT.md +++ b/docs/src/content/docs/commands/ZCOUNT.md @@ -7,7 +7,7 @@ The ZCOUNT command in DiceDB counts the number of members in a sorted set at the ## Syntax -``` +```bash ZCOUNT key min max ``` diff --git a/docs/src/content/docs/commands/ZPOPMAX.md b/docs/src/content/docs/commands/ZPOPMAX.md index c0fd58b7d..1e1343742 100644 --- a/docs/src/content/docs/commands/ZPOPMAX.md +++ b/docs/src/content/docs/commands/ZPOPMAX.md @@ -7,7 +7,7 @@ The `ZPOPMAX` command in DiceDB is used to remove and return the members with th ## Syntax -``` +```bash ZPOPMAX key [count] ``` diff --git a/docs/src/content/docs/commands/ZREM.md b/docs/src/content/docs/commands/ZREM.md index 44acc5d66..53e8d9e22 100644 --- a/docs/src/content/docs/commands/ZREM.md +++ b/docs/src/content/docs/commands/ZREM.md @@ -7,7 +7,7 @@ The `ZREM` command in DiceDB is used to remove the specified members from the so ## Syntax -``` +```bash ZREM key member [member ...] ``` From 91277db2aa97cbb7d810105e14e578a3dd4c06cf Mon Sep 17 00:00:00 2001 From: Syed Abdul Mateen <72622483+SyedMa3@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:03:35 +0530 Subject: [PATCH 08/20] Migrating: ('EXPIRE', 'EXPIREAT', 'EXPIRETIME', 'TTL', 'PTTL') (#1149) --- docs/src/content/docs/commands/EXPIRE.md | 92 +-- docs/src/content/docs/commands/EXPIREAT.md | 84 +-- docs/src/content/docs/commands/EXPIRETIME.md | 64 +- docs/src/content/docs/commands/PTTL.md | 33 +- docs/src/content/docs/commands/TTL.md | 35 +- .../commands/async/expire_test.go | 243 -------- .../commands/async/expireat_test.go | 242 -------- .../commands/async/expiretime_test.go | 97 --- .../commands/resp/expire_test.go | 564 +++++++++++++++++ .../commands/{async => resp}/ttl_pttl_test.go | 2 +- .../commands/websocket/expire_test.go | 585 ++++++++++++++++++ .../commands/websocket/ttl_pttl_test.go | 85 +++ internal/eval/commands.go | 61 +- internal/eval/eval.go | 232 ------- internal/eval/eval_test.go | 578 +++++++++++------ internal/eval/store_eval.go | 233 +++++++ internal/server/cmd_meta.go | 25 + internal/store/expire.go | 84 +++ internal/worker/cmd_meta.go | 22 +- 19 files changed, 2120 insertions(+), 1241 deletions(-) delete mode 100644 integration_tests/commands/async/expire_test.go delete mode 100644 integration_tests/commands/async/expireat_test.go delete mode 100644 integration_tests/commands/async/expiretime_test.go create mode 100644 integration_tests/commands/resp/expire_test.go rename integration_tests/commands/{async => resp}/ttl_pttl_test.go (99%) create mode 100644 integration_tests/commands/websocket/expire_test.go create mode 100644 integration_tests/commands/websocket/ttl_pttl_test.go diff --git a/docs/src/content/docs/commands/EXPIRE.md b/docs/src/content/docs/commands/EXPIRE.md index e8612d2cf..d63efa4be 100644 --- a/docs/src/content/docs/commands/EXPIRE.md +++ b/docs/src/content/docs/commands/EXPIRE.md @@ -22,10 +22,7 @@ EXPIRE key seconds [NX | XX | GT | LT] | `GT` | Set expiry only if the new expiry is greater than the current one. | None | No | | `LT` | Set expiry only if the new expiry is less than the current one. | None | No | - -## Return Value - -The `EXPIRE` command returns an integer value: +## Return Values | Condition | Return Value | |------------------------------------------------|---------------------------------------------------| @@ -34,115 +31,72 @@ The `EXPIRE` command returns an integer value: ## Behaviour -When the `EXPIRE` command is issued: - -1. If the specified key exists, DiceDB sets a timeout on the key. The key will be automatically deleted after the specified number of seconds. -2. If the key does not exist, no timeout is set, and the command returns `0`. -3. If the key already has a timeout, the existing timeout is replaced with the new one specified by the `EXPIRE` command. +- When a key exists, DiceDB sets a timeout on it. The key will be automatically deleted after the specified seconds. +- If the key doesn't exist, no timeout is set, and the command returns `0`. +- If the key already has a timeout, the existing timeout is replaced with the new one. +- Conditional flags (NX, XX, GT, LT) control when the expiry can be set based on existing timeouts. -## Error Handling +## Errors -The `EXPIRE` command can raise errors in the following scenarios: - -1. `Syntax Error`: If the command is not used with the correct number of arguments, DiceDB will return a syntax error. - - Error Message: `(error) ERROR wrong number of arguments for 'expire' command` - - Returned if the command is issued with an incorrect number of arguments. +1. `Syntax Error`: + - Error Message: `(error) ERROR wrong number of arguments for 'expire' command` + - Returned if the command is issued with an incorrect number of arguments. ## Example Usage -### Setting a Timeout on a Key - -```bash -127.0.0.1:7379> SET mykey "Hello" -OK -``` -```bash -127.0.0.1:7379> EXPIRE mykey 10 -(integer) 1 -``` - -In this example, the key `mykey` is set with the value "Hello". The `EXPIRE` command sets a timeout of 10 seconds on `mykey`. After 10 seconds, `mykey` will be automatically deleted. - -### Checking if Timeout was Set +### Basic Usage +This example demonstrates the fundamental usage of the EXPIRE command. First, we set a key with a value, then set it to expire in 10 seconds. The TTL command shows the remaining time to live. ```bash 127.0.0.1:7379> SET mykey "Hello" OK -``` -```bash 127.0.0.1:7379> EXPIRE mykey 10 (integer) 1 -``` -```bash 127.0.0.1:7379> TTL mykey (integer) 10 ``` -The `TTL` command shows the remaining time to live for mykey, which is 10 seconds. +### Using Conditional Flags +This example shows how to use the NX and XX flags to conditionally set expiration times. NX sets the expiry only when there isn't one, while XX updates an existing expiry. -### Setting Expiry with Conditions (NX and XX) ```bash 127.0.0.1:7379> SET mykey "Hello" OK -``` -```bash 127.0.0.1:7379> EXPIRE mykey 10 NX (integer) 1 -``` -```bash 127.0.0.1:7379> EXPIRE mykey 20 XX (integer) 1 ``` -The `NX` option sets the expiry only if there was no expiry set, and the `XX` option updates it because there was an existing expiry. - ### Replacing an Existing Timeout - +This example illustrates how EXPIRE can replace an existing timeout with a new value. The final TTL command confirms the updated expiration time. ```bash 127.0.0.1:7379> SET mykey "Hello" OK -``` - -```bash 127.0.0.1:7379> EXPIRE mykey 10 (integer) 1 -``` - -```bash 127.0.0.1:7379> EXPIRE mykey 20 (integer) 1 -``` - -```bash 127.0.0.1:7379> TTL mykey (integer) 20 ``` -The initial `EXPIRE` command sets a timeout of 10 seconds. The subsequent `EXPIRE` command replaces the existing timeout with a new timeout of 20 seconds. - -### Attempting to Set Timeout on a Non-Existent Key +### Invalid Key +This example shows what happens when trying to set an expiration on a non-existent key. The command returns 0 to indicate failure. ```bash 127.0.0.1:7379> EXPIRE non_existent_key 10 (integer) 0 ``` -The command returns `0`, indicating that the key does not exist and no timeout was set. - -## Error Handling Examples - -### Syntax Error - -```bash -127.0.0.1:7379> EXPIRE mykey -(error) ERROR wrong number of arguments for 'expire' command -``` +## Best Practices -This example shows a syntax error due to missing the `seconds` argument. +- Use `TTL` command to check remaining time before expiration +- Consider using `PERSIST` command to remove expiration if needed +- Choose appropriate conditional flags (NX, XX, GT, LT) based on your use case +- Ensure timeout values are appropriate for your application's needs -## Additional Notes +## Alternatives -- The `EXPIRE` command is often used in conjunction with other commands like `SET`, `GET`, and `DEL` to manage the lifecycle of keys in DiceDB. -- The timeout can be removed by using the `PERSIST` command, which removes the expiration from the key. -- The `TTL` command can be used to check the remaining time to live of a key with an expiration. +- Use `EXPIREAT` command for more precise expiration control based on Unix timestamps diff --git a/docs/src/content/docs/commands/EXPIREAT.md b/docs/src/content/docs/commands/EXPIREAT.md index b5226f0b1..bb233202b 100644 --- a/docs/src/content/docs/commands/EXPIREAT.md +++ b/docs/src/content/docs/commands/EXPIREAT.md @@ -22,68 +22,43 @@ EXPIREAT key timestamp [NX|XX|GT|LT] | `GT` | Set the expiration only if the new expiration time is greater than or equal to the current one. | None | No | | `LT` | Set the expiration only if the new expiration time is less than the current one. | None | No | -## Return values +## Return Values -| Condition | Return Value | -| -------------------------------------------------- | ------------ | -| Command is successful | `1` | -| Key does not exist or the timeout could not be set | `0` | -| Syntax or specified constraints are invalid | error | +| Condition | Return Value | +|------------------------------------------------|---------------------------------------------------| +| Timeout was successfully set. | `1` | +| Timeout was not set (e.g., key does not exist, or conditions not met).| `0` | ## Behaviour - When the `EXPIREAT` command is executed, DiceDB will set the expiration time of the specified key to the given Unix timestamp. - If the key already has an expiration time, it will be overwritten with the new timestamp. -- If the key does not exist, the command will return `0` and no expiration time will be set. +- If the key does not exist, no timeout is set, and the command returns `0`. +- Conditional flags (NX, XX, GT, LT) control when the expiry can be set based on existing timeouts. ## Errors -### Wrong number of arguments +1. `Syntax Error`: + - Error Message: `(error) ERROR wrong number of arguments for 'expireat' command` + - Returned if the command is issued with an incorrect number of arguments. -When the `EXPIREAT` command is called with the wrong number of arguments, an error is returned. +2. `Invalid Timestamp`: + - Error Message: `(error) ERROR value is not an integer or out of range` + - Returned if the timestamp is not a valid integer. -```bash -127.0.0.1:7379> EXPIREAT testkey1 -(error) ERROR wrong number of arguments for 'EXPIREAT' command -``` - -### Invalid timestamp - -When the provided timestamp is not a valid integer, an error is returned. - -```bash -127.0.0.1:7379> EXPIREAT testkey1 17282112781a -(error) ERROR value is not an integer or out of range -``` - -### Invalid format of Unix Timestamp - -When the provided timestamp is not a valid Unix timestamp, or is outside the supported range, an error is returned. - -```bash -127.0.0.1:7379> EXPIREAT testkey1 11111111111111111 -(error) ERROR invalid expire time in 'EXPIREAT' command -``` +3. `Invalid Unix Timestamp Format`: + - Error Message: `(error) ERROR invalid expire time in 'EXPIREAT' command` + - Returned if the timestamp is outside the supported range. ## Example Usage -### Setting an Expiration Time - -Setting a key `mykey` to expire at the Unix timestamp `17282126871`. +### Basic Usage +This example demonstrates setting a key to expire at a specific Unix timestamp. ```bash 127.0.0.1:7379> SET mykey "Hello" OK -127.0.0.1:7379> EXPIREAT mykey 17282126871 -(integer) 1 -``` - -### Checking the expiration time - -Checking the remaining time to live of a key (in seconds). - -```bash -127.0.0.1:7379> EXPIREAT mykey 17282126871 +127.0.0.1:7379> EXPIREAT mykey 1728212687 (integer) 1 127.0.0.1:7379> TTL mykey (integer) 15553913293 @@ -94,7 +69,7 @@ Checking the remaining time to live of a key (in seconds). Trying to set an expiration time for a non-existing key. ```bash -127.0.0.1:7379> EXPIREAT nonexistingkey 17282126871 +127.0.0.1:7379> EXPIREAT nonexistingkey 1728212687 (integer) 0 ``` @@ -156,17 +131,14 @@ OK (integer) 1 ``` -## Additional notes - -- The `EXPIREAT` command is useful when you need to synchronize the expiration of keys across multiple DiceDB instances or when you need to set an expiration time based on an external event that provides a Unix timestamp. -- The timestamp should be in seconds. If you have a timestamp in milliseconds, you need to convert it to seconds before using it with `EXPIREAT`. -- There is an arbitrary limit to the size of the `unix-time-seconds` of [9223372036854775](https://github.com/DiceDB/dice/blob/b74dc8ffd5e518eaa9b82020d2b25a592c6472d4/internal/eval/eval.go#L69). +## Best Practices -## Related commands +- Use `TTL` command to check remaining time before expiration +- Consider using `PERSIST` command to remove expiration if needed +- Choose appropriate conditional flags (NX, XX, GT, LT) based on your use case +- Ensure Unix timestamps are in seconds, not milliseconds +- Be aware of the timestamp limit of [9223372036854775](https://github.com/DiceDB/dice/blob/b74dc8ffd5e518eaa9b82020d2b25a592c6472d4/internal/eval/eval.go#L69) -- `EXPIRE`: Sets the expiration time of a key in seconds from the current time. -- `PEXPIREAT`: Sets the expiration time of a key as an absolute Unix timestamp in milliseconds. -- `TTL`: Returns the remaining time to 127.0.0.1:7379 of a key in seconds. -- `PTTL`: Returns the remaining time to live of a key in milliseconds. +## Alternatives -By understanding and using the `EXPIREAT` command, you can effectively manage the lifecycle of keys in your DiceDB database, ensuring that data is available only as long as it is needed. +- Use `EXPIRE` command for simpler expiration control based on relative time \ No newline at end of file diff --git a/docs/src/content/docs/commands/EXPIRETIME.md b/docs/src/content/docs/commands/EXPIRETIME.md index 1c226a08b..da46e3a23 100644 --- a/docs/src/content/docs/commands/EXPIRETIME.md +++ b/docs/src/content/docs/commands/EXPIRETIME.md @@ -1,6 +1,6 @@ --- title: EXPIRETIME -description: Documentation for the DiceDB command EXPIRETIME +description: The `EXPIRETIME` command in DiceDB is used to retrieve the absolute Unix timestamp (in seconds) at which a given key will expire. This command is particularly useful for understanding the exact expiration time of a key, which can help in debugging and managing key lifetimes. --- The `EXPIRETIME` command in DiceDB is used to retrieve the absolute Unix timestamp (in seconds) at which a given key will expire. This command is particularly useful for understanding the exact expiration time of a key, which can help in debugging and managing key lifetimes. @@ -17,7 +17,7 @@ EXPIRETIME key |-----------|---------------------------------------------------------------------------|---------|----------| | `key` | The name of the key whose expiration time is to be retrieved | String | Yes | -## Return Value +## Return Values | Condition | Return Value | |------------------------------------------------|---------------------------------------------------| @@ -25,82 +25,54 @@ EXPIRETIME key | The key exists but has no expiration time | -1 | | The key does not exist | -2 | - ## Behaviour -When the `EXPIRETIME` command is executed: - -1. DiceDB checks if the specified key exists in the database. -2. If the key exists and has an associated expiration time, DiceDB returns the absolute Unix timestamp (in seconds) at which the key will expire. -3. If the key exists without an expiration time, the command returns `-1`. -4. If the key doesn't exist, the command returns `-2`. +- DiceDB checks if the specified key exists in the database +- If the key exists and has an associated expiration time, DiceDB returns the absolute Unix timestamp (in seconds) +- If the key exists without an expiration time, the command returns `-1` +- If the key doesn't exist, the command returns `-2` -## Error Handling +## Errors -The `EXPIRETIME` command can raise errors in the following scenarios: - -1. `Wrong number of arguments`: If the command is called with an incorrect number of arguments, DiceDB will return an error message: - ``` - (error) ERROR wrong number of arguments for 'expiretime' command - ``` -2. `Invalid key type`: If the key is not a valid string, DiceDB will return an error message: - ``` - (error) ERROR invalid key type - ``` +1. `Wrong number of arguments`: + - Error Message: `(error) ERROR wrong number of arguments for 'expiretime' command` + - Occurs when the command is called with an incorrect number of arguments ## Example Usage -### Example 1: Key with Expiration Time +### Key with Expiration Time ```bash 127.0.0.1:7379> SET mykey "Hello" OK -``` -```bash 127.0.0.1:7379> EXPIRE mykey 60 (integer) 1 -``` -```bash 127.0.0.1:7379> EXPIRETIME mykey -``` - -`Output:` - -``` (integer) 1728548993 ``` In this example, the key `mykey` is set with a value "Hello" and an expiration time of 60 seconds. The `EXPIRETIME` command returns the Unix timestamp at which `mykey` will expire. -### Example 2: Key without Expiration Time +### Key without Expiration Time ```bash 127.0.0.1:7379> SET mykey "Hello" OK -``` - -```bash 127.0.0.1:7379> EXPIRETIME mykey -``` - -`Output:` - -``` (integer) -1 ``` In this example, the key `mykey` is set with a value "Hello" but no expiration time is set. The `EXPIRETIME` command returns `-1` indicating that the key does not have an associated expiration time. -### Example 3: Non-Existent Key +### Non-Existent Key ```bash 127.0.0.1:7379> EXPIRETIME nonExistentKey -``` - -`Output:` - -``` (integer) -2 ``` - In this example, the key `nonExistentKey` does not exist in the database. The `EXPIRETIME` command returns `-2` indicating that the key does not exist. + +## Alternatives + +- Use `TTL` to get relative expiration times +- Use `PTTL` to get relative expiration times in milliseconds diff --git a/docs/src/content/docs/commands/PTTL.md b/docs/src/content/docs/commands/PTTL.md index fffe3dacc..a24ea3d9b 100644 --- a/docs/src/content/docs/commands/PTTL.md +++ b/docs/src/content/docs/commands/PTTL.md @@ -12,19 +12,16 @@ PTTL key ``` ## Parameters - -| Parameter | Description | Type | Required | -|-----------------|--------------------------------------------------------------------------|---------|----------| -| `key` | The key for which the remaining TTL is to be retrieved. | String | Yes | +| Parameter | Description | Type | Required | +|-----------|--------------------------------------------------------------|--------|----------| +| `key` | The key for which the remaining TTL is to be retrieved | String | Yes | ## Return values - -| Condition | Return Value | -|------------------------------------------------------------|-------------------| -| Command is successful | Returns a positive integer value representing the remaining time to live of the key in milliseconds | -| The key exists but has no associated expiration | `-1` | -| The key does not exist | `-2` | - +| Condition | Return Value | +|-----------------------------------------------------|--------------------------------------------------------------------------------------------| +| Command is successful | Positive integer representing the remaining time to live of the key in milliseconds | +| Key exists but has no associated expiration | `-1` | +| Key does not exist | `-2` | ## Behaviour @@ -42,7 +39,7 @@ PTTL key ## Example Usage -### Example 1: Key with Expiration +### Key with Expiration ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -53,7 +50,7 @@ PTTL key In this example, the key `mykey` is set with a value of "Hello" and an expiration of 10 seconds. The `PTTL` command returns `10000`, indicating that the key will expire in 10000 milliseconds. -### Example 2: Key without Expiration +### Key without Expiration ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -63,7 +60,7 @@ In this example, the key `mykey` is set with a value of "Hello" and an expiratio In this example, the key `mykey` is set with a value of "Hello" but no expiration is set. The `PTTL` command returns `-1`, indicating that the key exists but has no associated expiration. -### Example 3: Non-existent Key +### Non-existent Key ```bash 127.0.0.1:7379> PTTL nonExistentKey @@ -72,7 +69,7 @@ In this example, the key `mykey` is set with a value of "Hello" but no expiratio In this example, the key `nonExistentKey` does not exist in the DiceDB database. The `PTTL` command returns `-2`, indicating that the key does not exist. -### Example 4: Invalid usage +### Invalid usage ```bash 127.0.0.1:7379> SET newkey "value" @@ -81,3 +78,9 @@ In this example, the key `nonExistentKey` does not exist in the DiceDB database. ``` In this example, the `PTTL` command is used with an extra argument. This results in an error, as the `PTTL` command accepts only one argument. + +## Best Practices +- Use `PTTL` in conjunction with `EXPIRE` or `PEXPIRE` commands to manage key expiration effectively + +## Alternatives +- `TTL`: Similar to `PTTL` but returns the time-to-live in seconds instead of milliseconds diff --git a/docs/src/content/docs/commands/TTL.md b/docs/src/content/docs/commands/TTL.md index 1bac81162..a412d48b7 100644 --- a/docs/src/content/docs/commands/TTL.md +++ b/docs/src/content/docs/commands/TTL.md @@ -33,16 +33,13 @@ TTL key ## Errors -The `TTL` command can raise errors in the following scenarios: +1. `Wrong number of arguments`: + - Error Message: `(error) ERR wrong number of arguments for 'ttl' command` + - Occurs when attempting to use the command with incorrect number of arguments -- `Syntax Error`: +## Example Usage - - (error) ERROR syntax error - - Occurs when attempting to use the command with more than one argument. - -## Examples - -### Example 1: Key with Expiration +### Check TTL for key with expiration ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -51,9 +48,10 @@ The `TTL` command can raise errors in the following scenarios: (integer) 10 ``` -In this example, the key `mykey` is set with a value of "Hello" and an expiration time of 10 seconds. The `TTL` command returns `10`, indicating that the key will expire in 10 seconds. +In this example, a key `mykey` is created with a value "Hello". Then, an expiration time of 10 seconds is set using the `EXPIRE` command. When `TTL` is called on `mykey`, it returns 10, indicating that the key will expire in 10 seconds. + -### Example 2: Key without Expiration +### Check TTL for key without expiration ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -61,24 +59,29 @@ In this example, the key `mykey` is set with a value of "Hello" and an expiratio (integer) -1 ``` -Here, the key `mykey` is set with a value of "Hello" but no expiration time is set. The `TTL` command returns `-1`, indicating that the key has no expiration. +In this example, the key `mykey` is set with a value of "Hello" but no expiration is set. The `TTL` command returns `-1`, indicating that the key exists but has no associated expiration. -### Example 3: Non-Existent Key +### Check TTL for non-existent key ```bash 127.0.0.1:7379> TTL non_existent_key (integer) -2 ``` -In this example, the key `non_existent_key` does not exist in the database. The `TTL` command returns `-2`, indicating that the key does not exist. +In this example, the key `non_existent_key` does not exist in the DiceDB database. The `TTL` command returns `-2`, indicating that the key does not exist. +### Invalid usage -### Example 4: Invalid usage ```bash 127.0.0.1:7379> SET newkey "value" 127.0.0.1:7379> TTL newkey value (error) ERR wrong number of arguments for 'ttl' command ``` -- The `TTL` command requires exactly one argument: `key` -- Since only more than one argument is provided, DiceDB returns a syntax error. +In this example, the `TTL` command is used with an extra argument. This results in an error, as the `TTL` command accepts only one argument. + +## Best Practices +- Use `TTL` in conjunction with `EXPIRE` or `EXPIREAT` commands to manage key expiration effectively + +## Alternatives +- `PTTL`: Similar to `TTL` but returns the time-to-live in milliseconds instead of seconds diff --git a/integration_tests/commands/async/expire_test.go b/integration_tests/commands/async/expire_test.go deleted file mode 100644 index 129ef39ad..000000000 --- a/integration_tests/commands/async/expire_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package async - -import ( - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestExpire(t *testing.T) { - conn := getLocalConnection() - defer conn.Close() - - testCases := []struct { - name string - setup string - commands []string - expected []interface{} - delay []time.Duration - }{ - { - name: "Set with EXPIRE command", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key 1", - }, - expected: []interface{}{"OK", int64(1)}, - delay: []time.Duration{0, 0}, - }, - { - name: "Check if key is nil after expiration", - setup: "SET test_key test_value", - commands: []string{ - "EXPIRE test_key 1", - "GET test_key", - }, - expected: []interface{}{int64(1), "(nil)"}, - delay: []time.Duration{0, 1100 * time.Millisecond}, - }, - { - name: "EXPIRE non-existent key", - setup: "", - commands: []string{ - "EXPIRE non_existent_key 1", - }, - expected: []interface{}{int64(0)}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIRE with past time", - setup: "SET test_key test_value", - commands: []string{ - "EXPIRE test_key -1", - "GET test_key", - }, - expected: []interface{}{"ERR invalid expire time in 'expire' command", "test_value"}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIRE with invalid syntax", - setup: "SET test_key test_value", - commands: []string{ - "EXPIRE test_key", - }, - expected: []interface{}{"ERR wrong number of arguments for 'expire' command"}, - delay: []time.Duration{0}, - }, - { - name: "Test(NX): Set the expiration only if the key has no expiration time", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "Test(XX): Set the expiration only if the key already has an expiration time", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", - "TTL test_key", - "EXPIRE test_key " + strconv.FormatInt(10, 10), - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", - }, - expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - - { - name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " GT", - "TTL test_key", - "EXPIRE test_key " + strconv.FormatInt(10, 10), - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " GT", - }, - expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - - { - name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "TEST(NX + LT/GT)", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " LT", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " GT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "test_value"}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - { - name: "TEST(XX + LT/GT)", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(20, 10), - "EXPIRE test_key " + strconv.FormatInt(5, 10) + " XX" + " LT", - "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX" + " GT", - "EXPIRE test_key " + strconv.FormatInt(20, 10) + " XX" + " GT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), int64(1), int64(1), int64(1), "test_value"}, - delay: []time.Duration{0, 0, 0, 0, 0, 0}, - }, - { - name: "Test if value is nil after expiration", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(20, 10), - "EXPIRE test_key " + strconv.FormatInt(2, 10) + " XX" + " LT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), int64(1), "(nil)"}, - delay: []time.Duration{0, 0, 0, 2 * time.Second}, - }, - { - name: "Test if value is nil after expiration", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(2, 10) + " NX", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), "(nil)"}, - delay: []time.Duration{0, 0, 2 * time.Second}, - }, - { - name: "Invalid Command Test", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "rr", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "NX", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "xx", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "nx", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "gt", - "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "lt", - }, - expected: []interface{}{"OK", "ERR Unsupported option rr", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR GT and LT options at the same time are not compatible", - "ERR GT and LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible"}, - delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - if tc.setup != "" { - FireCommand(conn, tc.setup) - } - - // Execute commands - var results []interface{} - for i, cmd := range tc.commands { - // Wait if delay is specified - if tc.delay[i] > 0 { - time.Sleep(tc.delay[i]) - } - result := FireCommand(conn, cmd) - results = append(results, result) - } - - // Validate results - for i, expected := range tc.expected { - if i >= len(results) { - t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) - } - - if expected == "(nil)" { - assert.True(t, results[i] == "(nil)" || results[i] == "", - "Expected nil or empty result, got %v", results[i]) - } else { - assert.Equal(t, expected, results[i]) - } - } - }) - } -} diff --git a/integration_tests/commands/async/expireat_test.go b/integration_tests/commands/async/expireat_test.go deleted file mode 100644 index 4504e50fa..000000000 --- a/integration_tests/commands/async/expireat_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package async - -import ( - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestExpireat(t *testing.T) { - conn := getLocalConnection() - defer conn.Close() - - testCases := []struct { - name string - setup string - commands []string - expected []interface{} - delay []time.Duration - }{ - { - name: "Set with EXPIREAT command", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), - }, - expected: []interface{}{"OK", int64(1)}, - delay: []time.Duration{0, 0}, - }, - { - name: "Check if key is nil after expiration", - setup: "SET test_key test_value", - commands: []string{ - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), - "GET test_key", - }, - expected: []interface{}{int64(1), "(nil)"}, - delay: []time.Duration{0, 1100 * time.Millisecond}, - }, - { - name: "EXPIREAT non-existent key", - setup: "", - commands: []string{ - "EXPIREAT non_existent_key " + strconv.FormatInt(time.Now().Unix()+1, 10), - }, - expected: []interface{}{int64(0)}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIREAT with past time", - setup: "SET test_key test_value", - commands: []string{ - "EXPIREAT test_key " + strconv.FormatInt(-1, 10), - "GET test_key", - }, - expected: []interface{}{"ERR invalid expire time in 'expireat' command", "test_value"}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIREAT with invalid syntax", - setup: "SET test_key test_value", - commands: []string{ - "EXPIREAT test_key", - }, - expected: []interface{}{"ERR wrong number of arguments for 'expireat' command"}, - delay: []time.Duration{0}, - }, - { - name: "Test(NX): Set the expiration only if the key has no expiration time", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " NX", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " NX", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "Test(XX): Set the expiration only if the key already has an expiration time", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", - "TTL test_key", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", - }, - expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - - { - name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " GT", - "TTL test_key", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " GT", - }, - expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - - { - name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", - }, - expected: []interface{}{"OK", int64(1), int64(0)}, - delay: []time.Duration{0, 0, 0}, - }, - - { - name: "TEST(NX + LT/GT)", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " LT", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " GT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "test_value"}, - delay: []time.Duration{0, 0, 0, 0, 0}, - }, - { - name: "TEST(XX + LT/GT)", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+5, 10) + " XX" + " LT", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX" + " GT", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " XX" + " GT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), int64(1), int64(1), int64(1), "test_value"}, - delay: []time.Duration{0, 0, 0, 0, 0, 0}, - }, - { - name: "Test if value is nil after expiration", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " XX" + " LT", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), int64(1), "(nil)"}, - delay: []time.Duration{0, 0, 0, 2 * time.Second}, - }, - { - name: "Test if value is nil after expiration", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " NX", - "GET test_key", - }, - expected: []interface{}{"OK", int64(1), "(nil)"}, - delay: []time.Duration{0, 0, 2 * time.Second}, - }, - { - name: "Invalid Command Test", - setup: "", - commands: []string{ - "SET test_key test_value", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "rr", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "NX", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "xx", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "nx", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "gt", - "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "lt", - }, - expected: []interface{}{"OK", "ERR Unsupported option rr", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR GT and LT options at the same time are not compatible", - "ERR GT and LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible", - "ERR NX and XX, GT or LT options at the same time are not compatible"}, - delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - if tc.setup != "" { - FireCommand(conn, tc.setup) - } - - // Execute commands - var results []interface{} - for i, cmd := range tc.commands { - // Wait if delay is specified - if tc.delay[i] > 0 { - time.Sleep(tc.delay[i]) - } - result := FireCommand(conn, cmd) - results = append(results, result) - } - // Validate results - for i, expected := range tc.expected { - if i >= len(results) { - t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) - } - - if expected == "(nil)" { - assert.True(t, results[i] == "(nil)" || results[i] == "", - "Expected nil or empty result, got %v", results[i]) - } else { - assert.Equal(t, expected, results[i]) - } - } - }) - } -} diff --git a/integration_tests/commands/async/expiretime_test.go b/integration_tests/commands/async/expiretime_test.go deleted file mode 100644 index 9ee7fb8ed..000000000 --- a/integration_tests/commands/async/expiretime_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package async - -import ( - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestExpiretime(t *testing.T) { - conn := getLocalConnection() - defer conn.Close() - - futureUnixTimestamp := time.Now().Unix() + 1 - - testCases := []struct { - name string - setup string - commands []string - expected []interface{} - delay []time.Duration - }{ - { - name: "EXPIRETIME command", - setup: "SET test_key test_value", - commands: []string{ - "EXPIREAT test_key " + strconv.FormatInt(futureUnixTimestamp, 10), - "EXPIRETIME test_key", - }, - expected: []interface{}{int64(1), futureUnixTimestamp}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIRETIME non-existent key", - setup: "", - commands: []string{ - "EXPIRETIME non_existent_key", - }, - expected: []interface{}{int64(-2)}, - delay: []time.Duration{0}, - }, - { - name: "EXPIRETIME with past time", - setup: "SET test_key test_value", - commands: []string{ - "EXPIREAT test_key 1724167183", - "EXPIRETIME test_key", - }, - expected: []interface{}{int64(1), int64(-2)}, - delay: []time.Duration{0, 0}, - }, - { - name: "EXPIRETIME with invalid syntax", - setup: "SET test_key test_value", - commands: []string{ - "EXPIRETIME", - "EXPIRETIME key1 key2", - }, - expected: []interface{}{"ERR wrong number of arguments for 'expiretime' command", "ERR wrong number of arguments for 'expiretime' command"}, - delay: []time.Duration{0, 0}, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // Setup - if tc.setup != "" { - FireCommand(conn, tc.setup) - } - - // Execute commands - var results []interface{} - for i, cmd := range tc.commands { - // Wait if delay is specified - if tc.delay[i] > 0 { - time.Sleep(tc.delay[i]) - } - result := FireCommand(conn, cmd) - results = append(results, result) - } - - // Validate results - for i, expected := range tc.expected { - if i >= len(results) { - t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) - } - - if expected == "(nil)" { - assert.True(t, results[i] == "(nil)" || results[i] == "", - "Expected nil or empty result, got %v", results[i]) - } else { - assert.Equal(t, expected, results[i]) - } - } - }) - } -} diff --git a/integration_tests/commands/resp/expire_test.go b/integration_tests/commands/resp/expire_test.go new file mode 100644 index 000000000..12b2e71c9 --- /dev/null +++ b/integration_tests/commands/resp/expire_test.go @@ -0,0 +1,564 @@ +package resp + +import ( + "strconv" + "testing" + "time" + + "gotest.tools/v3/assert" +) + +func TestEXPIRE(t *testing.T) { + conn := getLocalConnection() + defer conn.Close() + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "Set with EXPIRE command", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key 1", + }, + expected: []interface{}{"OK", int64(1)}, + delay: []time.Duration{0, 0}, + }, + { + name: "Check if key is nil after expiration", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key 1", + "GET test_key", + }, + expected: []interface{}{int64(1), "(nil)"}, + delay: []time.Duration{0, 1100 * time.Millisecond}, + }, + { + name: "EXPIRE non-existent key", + setup: "", + commands: []string{ + "EXPIRE non_existent_key 1", + }, + expected: []interface{}{int64(0)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRE with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key -1", + "GET test_key", + }, + expected: []interface{}{"ERR invalid expire time in 'expire' command", "test_value"}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRE with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expire' command"}, + delay: []time.Duration{0}, + }, + { + name: "Test(NX): Set the expiration only if the key has no expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "Test(XX): Set the expiration only if the key already has an expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", + "TTL test_key", + "EXPIRE test_key " + strconv.FormatInt(10, 10), + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", + }, + expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " GT", + "TTL test_key", + "EXPIRE test_key " + strconv.FormatInt(10, 10), + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " GT", + }, + expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(NX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + { + name: "TEST(XX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10), + "EXPIRE test_key " + strconv.FormatInt(5, 10) + " XX" + " LT", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX" + " GT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " XX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), int64(1), int64(1), int64(1), "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10), + "EXPIRE test_key " + strconv.FormatInt(2, 10) + " XX" + " LT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), int64(1), "(nil)"}, + delay: []time.Duration{0, 0, 0, 2 * time.Second}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(2, 10) + " NX", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), "(nil)"}, + delay: []time.Duration{0, 0, 2 * time.Second}, + }, + { + name: "Invalid Command Test", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "rr", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "NX", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "xx", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "nx", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "gt", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "lt", + }, + expected: []interface{}{"OK", "ERR Unsupported option rr", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + FireCommand(conn, tc.setup) + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result := FireCommand(conn, cmd) + results = append(results, result) + } + + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == "(nil)" { + assert.Assert(t, results[i] == "(nil)" || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} + +func TestEXPIREAT(t *testing.T) { + conn := getLocalConnection() + defer conn.Close() + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "Set with EXPIREAT command", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + }, + expected: []interface{}{"OK", int64(1)}, + delay: []time.Duration{0, 0}, + }, + { + name: "Check if key is nil after expiration", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + "GET test_key", + }, + expected: []interface{}{int64(1), "(nil)"}, + delay: []time.Duration{0, 1100 * time.Millisecond}, + }, + { + name: "EXPIREAT non-existent key", + setup: "", + commands: []string{ + "EXPIREAT non_existent_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + }, + expected: []interface{}{int64(0)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIREAT with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(-1, 10), + "GET test_key", + }, + expected: []interface{}{"ERR invalid expire time in 'expireat' command", "test_value"}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIREAT with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expireat' command"}, + delay: []time.Duration{0}, + }, + { + name: "Test(NX): Set the expiration only if the key has no expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " NX", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "Test(XX): Set the expiration only if the key already has an expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", + "TTL test_key", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", + }, + expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " GT", + "TTL test_key", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " GT", + }, + expected: []interface{}{"OK", int64(0), int64(-1), int64(1), int64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", + }, + expected: []interface{}{"OK", int64(1), int64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(NX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + { + name: "TEST(XX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+5, 10) + " XX" + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX" + " GT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " XX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), int64(1), int64(1), int64(1), "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " XX" + " LT", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), int64(1), "(nil)"}, + delay: []time.Duration{0, 0, 0, 2 * time.Second}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " NX", + "GET test_key", + }, + expected: []interface{}{"OK", int64(1), "(nil)"}, + delay: []time.Duration{0, 0, 2 * time.Second}, + }, + { + name: "Invalid Command Test", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "rr", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "xx", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "nx", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "gt", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "lt", + }, + expected: []interface{}{"OK", "ERR Unsupported option rr", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + FireCommand(conn, tc.setup) + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result := FireCommand(conn, cmd) + results = append(results, result) + } + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == "(nil)" { + assert.Assert(t, results[i] == "(nil)" || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} + +func TestEXPIRETIME(t *testing.T) { + conn := getLocalConnection() + defer conn.Close() + + futureUnixTimestamp := time.Now().Unix() + 1 + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "EXPIRETIME command", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(futureUnixTimestamp, 10), + "EXPIRETIME test_key", + }, + expected: []interface{}{int64(1), futureUnixTimestamp}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRETIME non-existent key", + setup: "", + commands: []string{ + "EXPIRETIME non_existent_key", + }, + expected: []interface{}{int64(-2)}, + delay: []time.Duration{0}, + }, + { + name: "EXPIRETIME with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key 1724167183", + "EXPIRETIME test_key", + }, + expected: []interface{}{int64(1), int64(-2)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRETIME with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRETIME", + "EXPIRETIME key1 key2", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expiretime' command", "ERR wrong number of arguments for 'expiretime' command"}, + delay: []time.Duration{0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + FireCommand(conn, tc.setup) + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result := FireCommand(conn, cmd) + results = append(results, result) + } + + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == "(nil)" { + assert.Assert(t, results[i] == "(nil)" || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} diff --git a/integration_tests/commands/async/ttl_pttl_test.go b/integration_tests/commands/resp/ttl_pttl_test.go similarity index 99% rename from integration_tests/commands/async/ttl_pttl_test.go rename to integration_tests/commands/resp/ttl_pttl_test.go index ad5db6e5c..4853196d4 100644 --- a/integration_tests/commands/async/ttl_pttl_test.go +++ b/integration_tests/commands/resp/ttl_pttl_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/integration_tests/commands/websocket/expire_test.go b/integration_tests/commands/websocket/expire_test.go new file mode 100644 index 000000000..9cf9e8713 --- /dev/null +++ b/integration_tests/commands/websocket/expire_test.go @@ -0,0 +1,585 @@ +package websocket + +import ( + "strconv" + "testing" + "time" + + "gotest.tools/v3/assert" +) + +func TestEXPIRE(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "Set with EXPIRE command", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key 1", + }, + expected: []interface{}{"OK", float64(1)}, + delay: []time.Duration{0, 0}, + }, + { + name: "Check if key is nil after expiration", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key 1", + "GET test_key", + }, + expected: []interface{}{float64(1), nil}, + delay: []time.Duration{0, 1100 * time.Millisecond}, + }, + { + name: "EXPIRE non-existent key", + setup: "", + commands: []string{ + "EXPIRE non_existent_key 1", + }, + expected: []interface{}{float64(0)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRE with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key -1", + "GET test_key", + }, + expected: []interface{}{"ERR invalid expire time in 'expire' command", "test_value"}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRE with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRE test_key", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expire' command"}, + delay: []time.Duration{0}, + }, + { + name: "Test(NX): Set the expiration only if the key has no expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " NX", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "Test(XX): Set the expiration only if the key already has an expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", + "TTL test_key", + "EXPIRE test_key " + strconv.FormatInt(10, 10), + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX", + }, + expected: []interface{}{"OK", float64(0), float64(-1), float64(1), float64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " GT", + "TTL test_key", + "EXPIRE test_key " + strconv.FormatInt(10, 10), + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " GT", + }, + expected: []interface{}{"OK", float64(0), float64(-1), float64(1), float64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " LT", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(NX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " LT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " NX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + { + name: "TEST(XX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10), + "EXPIRE test_key " + strconv.FormatInt(5, 10) + " XX" + " LT", + "EXPIRE test_key " + strconv.FormatInt(10, 10) + " XX" + " GT", + "EXPIRE test_key " + strconv.FormatInt(20, 10) + " XX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), float64(1), float64(1), float64(1), "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(20, 10), + "EXPIRE test_key " + strconv.FormatInt(2, 10) + " XX" + " LT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), float64(1), nil}, + delay: []time.Duration{0, 0, 0, 2 * time.Second}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(2, 10) + " NX", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), nil}, + delay: []time.Duration{0, 0, 2 * time.Second}, + }, + { + name: "Invalid Command Test", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "rr", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " XX" + " " + "NX", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "xx", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " GT" + " " + "lt" + " " + "nx", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "gt", + "EXPIRE test_key " + strconv.FormatInt(1, 10) + " nx" + " " + "xx" + " " + "lt", + }, + expected: []interface{}{"OK", "ERR Unsupported option rr", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + _, err := exec.FireCommandAndReadResponse(conn, tc.setup) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result, err := exec.FireCommandAndReadResponse(conn, cmd) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + results = append(results, result) + } + + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == nil { + assert.Assert(t, results[i] == nil || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} + +func TestEXPIREAT(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "Set with EXPIREAT command", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + }, + expected: []interface{}{"OK", float64(1)}, + delay: []time.Duration{0, 0}, + }, + { + name: "Check if key is nil after expiration", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + "GET test_key", + }, + expected: []interface{}{float64(1), nil}, + delay: []time.Duration{0, 1100 * time.Millisecond}, + }, + { + name: "EXPIREAT non-existent key", + setup: "", + commands: []string{ + "EXPIREAT non_existent_key " + strconv.FormatInt(time.Now().Unix()+1, 10), + }, + expected: []interface{}{float64(0)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIREAT with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(-1, 10), + "GET test_key", + }, + expected: []interface{}{"ERR invalid expire time in 'expireat' command", "test_value"}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIREAT with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expireat' command"}, + delay: []time.Duration{0}, + }, + { + name: "Test(NX): Set the expiration only if the key has no expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " NX", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "Test(XX): Set the expiration only if the key already has an expiration time", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", + "TTL test_key", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX", + }, + expected: []interface{}{"OK", float64(0), float64(-1), float64(1), float64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(GT): Set the expiration only if the new expiration time is greater than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " GT", + "TTL test_key", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " GT", + }, + expected: []interface{}{"OK", float64(0), float64(-1), float64(1), float64(1)}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(LT): Set the expiration only if the new expiration time is less than the current one", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " LT", + }, + expected: []interface{}{"OK", float64(1), float64(0)}, + delay: []time.Duration{0, 0, 0}, + }, + + { + name: "TEST(NX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " NX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0}, + }, + { + name: "TEST(XX + LT/GT)", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+5, 10) + " XX" + " LT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+10, 10) + " XX" + " GT", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10) + " XX" + " GT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), float64(1), float64(1), float64(1), "test_value"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+20, 10), + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " XX" + " LT", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), float64(1), nil}, + delay: []time.Duration{0, 0, 0, 2 * time.Second}, + }, + { + name: "Test if value is nil after expiration", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+2, 10) + " NX", + "GET test_key", + }, + expected: []interface{}{"OK", float64(1), nil}, + delay: []time.Duration{0, 0, 2 * time.Second}, + }, + { + name: "Invalid Command Test", + setup: "", + commands: []string{ + "SET test_key test_value", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "rr", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " XX" + " " + "NX", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "xx", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " GT" + " " + "lt" + " " + "nx", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "gt", + "EXPIREAT test_key " + strconv.FormatInt(time.Now().Unix()+1, 10) + " nx" + " " + "xx" + " " + "lt", + }, + expected: []interface{}{"OK", "ERR Unsupported option rr", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR GT and LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible", + "ERR NX and XX, GT or LT options at the same time are not compatible"}, + delay: []time.Duration{0, 0, 0, 0, 0, 0, 0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + _, err := exec.FireCommandAndReadResponse(conn, tc.setup) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result, err := exec.FireCommandAndReadResponse(conn, cmd) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + results = append(results, result) + } + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == nil { + assert.Assert(t, results[i] == nil || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} + +func TestEXPIRETIME(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + futureUnixTimestamp := time.Now().Unix() + 1 + + testCases := []struct { + name string + setup string + commands []string + expected []interface{} + delay []time.Duration + }{ + { + name: "EXPIRETIME command", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key " + strconv.FormatInt(futureUnixTimestamp, 10), + "EXPIRETIME test_key", + }, + expected: []interface{}{float64(1), float64(futureUnixTimestamp)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRETIME non-existent key", + setup: "", + commands: []string{ + "EXPIRETIME non_existent_key", + }, + expected: []interface{}{float64(-2)}, + delay: []time.Duration{0}, + }, + { + name: "EXPIRETIME with past time", + setup: "SET test_key test_value", + commands: []string{ + "EXPIREAT test_key 1724167183", + "EXPIRETIME test_key", + }, + expected: []interface{}{float64(1), float64(-2)}, + delay: []time.Duration{0, 0}, + }, + { + name: "EXPIRETIME with invalid syntax", + setup: "SET test_key test_value", + commands: []string{ + "EXPIRETIME", + "EXPIRETIME key1 key2", + }, + expected: []interface{}{"ERR wrong number of arguments for 'expiretime' command", "ERR wrong number of arguments for 'expiretime' command"}, + delay: []time.Duration{0, 0}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Setup + if tc.setup != "" { + _, err := exec.FireCommandAndReadResponse(conn, tc.setup) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + } + + // Execute commands + var results []interface{} + for i, cmd := range tc.commands { + // Wait if delay is specified + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result, err := exec.FireCommandAndReadResponse(conn, cmd) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + results = append(results, result) + } + + // Validate results + for i, expected := range tc.expected { + if i >= len(results) { + t.Fatalf("Not enough results. Expected %d, got %d", len(tc.expected), len(results)) + } + + if expected == nil { + assert.Assert(t, results[i] == nil || results[i] == "", + "Expected nil or empty result, got %v", results[i]) + } else { + assert.DeepEqual(t, expected, results[i]) + } + } + }) + } +} diff --git a/integration_tests/commands/websocket/ttl_pttl_test.go b/integration_tests/commands/websocket/ttl_pttl_test.go new file mode 100644 index 000000000..a87ef2208 --- /dev/null +++ b/integration_tests/commands/websocket/ttl_pttl_test.go @@ -0,0 +1,85 @@ +package websocket + +import ( + "testing" + "time" + + "gotest.tools/v3/assert" +) + +func TestTTLPTTL(t *testing.T) { + exec := NewWebsocketCommandExecutor() + conn := exec.ConnectToServer() + defer conn.Close() + + testCases := []struct { + name string + commands []string + expected []interface{} + assertType []string + delay []time.Duration + }{ + { + name: "TTL Simple Value", + commands: []string{"SET foo bar", "GETEX foo ex 5", "GETEX foo", "TTL foo"}, + expected: []interface{}{"OK", "bar", "bar", float64(5)}, + assertType: []string{"equal", "equal", "equal", "assert"}, + delay: []time.Duration{0, 0, 0, 0}, + }, + { + name: "PTTL Simple Value", + commands: []string{"SET foo bar", "GETEX foo px 5000", "GETEX foo", "PTTL foo"}, + expected: []interface{}{"OK", "bar", "bar", float64(5000)}, + assertType: []string{"equal", "equal", "equal", "assert"}, + delay: []time.Duration{0, 0, 0, 0}, + }, + { + name: "TTL & PTTL Non-Existent Key", + commands: []string{"TTL foo", "PTTL foo"}, + expected: []interface{}{float64(-2), float64(-2)}, + assertType: []string{"equal", "equal"}, + delay: []time.Duration{0, 0}, + }, + { + name: "TTL & PTTL without Expiry", + commands: []string{"SET foo bar", "GET foo", "TTL foo", "PTTL foo"}, + expected: []interface{}{"OK", "bar", float64(-1), float64(-1)}, + assertType: []string{"equal", "equal", "equal", "equal"}, + delay: []time.Duration{0, 0, 0, 0}, + }, + { + name: "TTL & PTTL with Persist", + commands: []string{"SET foo bar", "GETEX foo persist", "TTL foo", "PTTL foo"}, + expected: []interface{}{"OK", "bar", float64(-1), float64(-1)}, + assertType: []string{"equal", "equal", "equal", "equal"}, + delay: []time.Duration{0, 0, 0, 0}, + }, + { + name: "TTL & PTTL with Expire and Expired Key", + commands: []string{"SET foo bar", "GETEX foo ex 5", "GET foo", "TTL foo", "PTTL foo", "TTL foo", "PTTL foo"}, + expected: []interface{}{"OK", "bar", "bar", float64(5), float64(5000), float64(-2), float64(-2)}, + assertType: []string{"equal", "equal", "equal", "assert", "assert", "equal", "equal"}, + delay: []time.Duration{0, 0, 0, 0, 0, 5 * time.Second, 0}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + DeleteKey(t, conn, exec, "foo") + for i, cmd := range tc.commands { + if tc.delay[i] > 0 { + time.Sleep(tc.delay[i]) + } + result, err := exec.FireCommandAndReadResponse(conn, cmd) + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + if tc.assertType[i] == "equal" { + assert.DeepEqual(t, tc.expected[i], result) + } else if tc.assertType[i] == "assert" { + assert.Assert(t, result.(float64) <= tc.expected[i].(float64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + } + } + }) + } +} diff --git a/internal/eval/commands.go b/internal/eval/commands.go index 2765d7313..220eb4d54 100644 --- a/internal/eval/commands.go +++ b/internal/eval/commands.go @@ -326,9 +326,10 @@ var ( RESP encoded time (in secs) remaining for the key to expire RESP encoded -2 stating key doesn't exist or key is expired RESP encoded -1 in case no expiration is set on the key`, - Eval: evalTTL, - Arity: 2, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalTTL, + IsMigrated: true, + Arity: 2, + KeySpecs: KeySpecs{BeginIndex: 1}, } delCmdMeta = DiceCmdMeta{ Name: "DEL", @@ -345,9 +346,31 @@ var ( The expiry time should be in integer format; if not, it returns encoded error response Returns RespOne if expiry was set on the key successfully. Once the time is lapsed, the key will be deleted automatically`, - Eval: evalEXPIRE, - Arity: -3, - KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, + NewEval: evalEXPIRE, + IsMigrated: true, + Arity: -3, + KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, + } + expiretimeCmdMeta = DiceCmdMeta{ + Name: "EXPIRETIME", + Info: `EXPIRETIME returns the absolute Unix timestamp (since January 1, 1970) in seconds + at which the given key will expire`, + NewEval: evalEXPIRETIME, + IsMigrated: true, + Arity: -2, + KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, + } + expireatCmdMeta = DiceCmdMeta{ + Name: "EXPIREAT", + Info: `EXPIREAT sets a expiry time(in unix-time-seconds) on the specified key in args + args should contain 2 values, key and the expiry time to be set for the key + The expiry time should be in integer format; if not, it returns encoded error response + Returns RespOne if expiry was set on the key successfully. + Once the time is lapsed, the key will be deleted automatically`, + NewEval: evalEXPIREAT, + IsMigrated: true, + Arity: -3, + KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, } helloCmdMeta = DiceCmdMeta{ Name: "HELLO", @@ -667,9 +690,10 @@ var ( RESP encoded time (in secs) remaining for the key to expire RESP encoded -2 stating key doesn't exist or key is expired RESP encoded -1 in case no expiration is set on the key`, - Eval: evalPTTL, - Arity: 2, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalPTTL, + IsMigrated: true, + Arity: 2, + KeySpecs: KeySpecs{BeginIndex: 1}, } hsetCmdMeta = DiceCmdMeta{ Name: "HSET", @@ -810,25 +834,6 @@ var ( Arity: -2, KeySpecs: KeySpecs{BeginIndex: 1}, } - expiretimeCmdMeta = DiceCmdMeta{ - Name: "EXPIRETIME", - Info: `EXPIRETIME returns the absolute Unix timestamp (since January 1, 1970) in seconds - at which the given key will expire`, - Eval: evalEXPIRETIME, - Arity: -2, - KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, - } - expireatCmdMeta = DiceCmdMeta{ - Name: "EXPIREAT", - Info: `EXPIREAT sets a expiry time(in unix-time-seconds) on the specified key in args - args should contain 2 values, key and the expiry time to be set for the key - The expiry time should be in integer format; if not, it returns encoded error response - Returns RespOne if expiry was set on the key successfully. - Once the time is lapsed, the key will be deleted automatically`, - Eval: evalEXPIREAT, - Arity: -3, - KeySpecs: KeySpecs{BeginIndex: 1, Step: 1}, - } lpushCmdMeta = DiceCmdMeta{ Name: "LPUSH", Info: "LPUSH pushes values into the left side of the deque", diff --git a/internal/eval/eval.go b/internal/eval/eval.go index f0370fbbb..290755027 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -1072,39 +1072,6 @@ func evalJSONINGEST(args []string, store *dstore.Store) []byte { return result } -// evalTTL returns Time-to-Live in secs for the queried key in args -// The key should be the only param in args else returns with an error -// Returns RESP encoded time (in secs) remaining for the key to expire -// -// RESP encoded -2 stating key doesn't exist or key is expired -// RESP encoded -1 in case no expiration is set on the key -func evalTTL(args []string, store *dstore.Store) []byte { - if len(args) != 1 { - return diceerrors.NewErrArity("TTL") - } - - key := args[0] - - obj := store.Get(key) - - // if key does not exist, return RESP encoded -2 denoting key does not exist - if obj == nil { - return clientio.RespMinusTwo - } - - // if object exist, but no expiration is set on it then send -1 - exp, isExpirySet := dstore.GetExpiry(obj, store) - if !isExpirySet { - return clientio.RespMinusOne - } - - // compute the time remaining for the key to expire and - // return the RESP encoded form of it - durationMs := exp - uint64(utils.GetCurrentTime().UnixMilli()) - - return clientio.Encode(int64(durationMs/1000), false) -} - // evalDEL deletes all the specified keys in args list // returns the count of total deleted keys after encoding func evalDEL(args []string, store *dstore.Store) []byte { @@ -1123,174 +1090,6 @@ func evalDEL(args []string, store *dstore.Store) []byte { return clientio.Encode(countDeleted, false) } -// evalEXPIRE sets an expiry time(in secs) on the specified key in args -// args should contain 2 values, key and the expiry time to be set for the key -// The expiry time should be in integer format; if not, it returns encoded error response -// Returns response.RespOne if expiry was set on the key successfully. -// Once the time is lapsed, the key will be deleted automatically -func evalEXPIRE(args []string, store *dstore.Store) []byte { - if len(args) <= 1 { - return diceerrors.NewErrArity("EXPIRE") - } - - key := args[0] - exDurationSec, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return diceerrors.NewErrWithMessage(diceerrors.IntOrOutOfRangeErr) - } - - if exDurationSec < 0 || exDurationSec > maxExDuration { - return diceerrors.NewErrExpireTime("EXPIRE") - } - - obj := store.Get(key) - - // 0 if the timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments - if obj == nil { - return clientio.RespZero - } - isExpirySet, err2 := evaluateAndSetExpiry(args[2:], utils.AddSecondsToUnixEpoch(exDurationSec), key, store) - - if isExpirySet { - return clientio.RespOne - } else if err2 != nil { - return err2 - } - return clientio.RespZero -} - -// evalEXPIRETIME returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire -// args should contain only 1 value, the key -// Returns expiration Unix timestamp in seconds. -// Returns -1 if the key exists but has no associated expiration time. -// Returns -2 if the key does not exist. -func evalEXPIRETIME(args []string, store *dstore.Store) []byte { - if len(args) != 1 { - return diceerrors.NewErrArity("EXPIRETIME") - } - - key := args[0] - - obj := store.Get(key) - - // -2 if key doesn't exist - if obj == nil { - return clientio.RespMinusTwo - } - - exTimeMili, ok := dstore.GetExpiry(obj, store) - // -1 if key doesn't have expiration time set - if !ok { - return clientio.RespMinusOne - } - - return clientio.Encode(int(exTimeMili/1000), false) -} - -// evalEXPIREAT sets a expiry time(in unix-time-seconds) on the specified key in args -// args should contain 2 values, key and the expiry time to be set for the key -// The expiry time should be in integer format; if not, it returns encoded error response -// Returns response.RespOne if expiry was set on the key successfully. -// Once the time is lapsed, the key will be deleted automatically -func evalEXPIREAT(args []string, store *dstore.Store) []byte { - if len(args) <= 1 { - return clientio.Encode(errors.New("ERR wrong number of arguments for 'expireat' command"), false) - } - - key := args[0] - exUnixTimeSec, err := strconv.ParseInt(args[1], 10, 64) - if exUnixTimeSec < 0 || exUnixTimeSec > maxExDuration { - return diceerrors.NewErrExpireTime("EXPIREAT") - } - - if err != nil { - return clientio.Encode(errors.New(diceerrors.InvalidIntErr), false) - } - - isExpirySet, err2 := evaluateAndSetExpiry(args[2:], exUnixTimeSec, key, store) - if isExpirySet { - return clientio.RespOne - } else if err2 != nil { - return err2 - } - return clientio.RespZero -} - -// NX: Set the expiration only if the key does not already have an expiration time. -// XX: Set the expiration only if the key already has an expiration time. -// GT: Set the expiration only if the new expiration time is greater than the current one. -// LT: Set the expiration only if the new expiration time is less than the current one. -// Returns Boolean True and error nil if expiry was set on the key successfully. -// Returns Boolean False and error nil if conditions didn't met. -// Returns Boolean False and error not-nil if invalid combination of subCommands or if subCommand is invalid -func evaluateAndSetExpiry(subCommands []string, newExpiry int64, key string, - store *dstore.Store, -) (shouldSetExpiry bool, err []byte) { - newExpInMilli := newExpiry * 1000 - var prevExpiry *uint64 = nil - var nxCmd, xxCmd, gtCmd, ltCmd bool - - obj := store.Get(key) - // key doesn't exist - if obj == nil { - return false, nil - } - shouldSetExpiry = true - // if no condition exists - if len(subCommands) == 0 { - store.SetUnixTimeExpiry(obj, newExpiry) - return shouldSetExpiry, nil - } - - expireTime, ok := dstore.GetExpiry(obj, store) - if ok { - prevExpiry = &expireTime - } - - for i := range subCommands { - subCommand := strings.ToUpper(subCommands[i]) - - switch subCommand { - case NX: - nxCmd = true - if prevExpiry != nil { - shouldSetExpiry = false - } - case XX: - xxCmd = true - if prevExpiry == nil { - shouldSetExpiry = false - } - case GT: - gtCmd = true - if prevExpiry == nil || *prevExpiry > uint64(newExpInMilli) { - shouldSetExpiry = false - } - case LT: - ltCmd = true - if prevExpiry != nil && *prevExpiry < uint64(newExpInMilli) { - shouldSetExpiry = false - } - default: - return false, diceerrors.NewErrWithMessage("Unsupported option " + subCommands[i]) - } - } - - if !nxCmd && gtCmd && ltCmd { - return false, diceerrors.NewErrWithMessage("GT and LT options at the same time are not compatible") - } - - if nxCmd && (xxCmd || gtCmd || ltCmd) { - return false, diceerrors.NewErrWithMessage("NX and XX," + - " GT or LT options at the same time are not compatible") - } - - if shouldSetExpiry { - store.SetUnixTimeExpiry(obj, newExpiry) - } - return shouldSetExpiry, nil -} - func evalHELLO(args []string, store *dstore.Store) []byte { if len(args) > 1 { return diceerrors.NewErrArity("HELLO") @@ -2304,37 +2103,6 @@ func evalGETEX(args []string, store *dstore.Store) []byte { return clientio.Encode(obj.Value, false) } -// evalPTTL returns Time-to-Live in millisecs for the queried key in args -// The key should be the only param in args else returns with an error -// Returns RESP encoded time (in secs) remaining for the key to expire -// -// RESP encoded -2 stating key doesn't exist or key is expired -// RESP encoded -1 in case no expiration is set on the key -func evalPTTL(args []string, store *dstore.Store) []byte { - if len(args) != 1 { - return diceerrors.NewErrArity("PTTL") - } - - key := args[0] - - obj := store.Get(key) - - if obj == nil { - return clientio.RespMinusTwo - } - - exp, isExpirySet := dstore.GetExpiry(obj, store) - - if !isExpirySet { - return clientio.RespMinusOne - } - - // compute the time remaining for the key to expire and - // return the RESP encoded form of it - durationMs := exp - uint64(utils.GetCurrentTime().UnixMilli()) - return clientio.Encode(int64(durationMs), false) -} - // evalHSET sets the specified fields to their // respective values in a hashmap stored at key // diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index cf28589ca..29b8a43ee 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -66,6 +66,7 @@ func TestEval(t *testing.T) { testEvalJSONARRAPPEND(t, store) testEvalJSONRESP(t, store) testEvalTTL(t, store) + testEvalPTTL(t, store) testEvalDel(t, store) testEvalPersist(t, store) testEvalEXPIRE(t, store) @@ -307,8 +308,8 @@ func testEvalSET(t *testing.T, store *dstore.Store) { migratedOutput: EvalResponse{Result: clientio.OK, Error: nil}, }, { - name: "key val pair and invalid EX and PX", - input: []string{"KEY", "VAL", Ex, "2", Px, "2000"}, + name: "key val pair and invalid EX and PX", + input: []string{"KEY", "VAL", Ex, "2", Px, "2000"}, migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR syntax error")}, }, { @@ -590,20 +591,48 @@ func testEvalGETSET(t *testing.T, store *dstore.Store) { func testEvalEXPIRE(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "nil value": { - input: nil, - output: []byte("-ERR wrong number of arguments for 'expire' command\r\n"), + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRE"), + }, }, "empty args": { - input: []string{}, - output: []byte("-ERR wrong number of arguments for 'expire' command\r\n"), + input: []string{}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRE"), + }, }, "wrong number of args": { - input: []string{"KEY1"}, - output: []byte("-ERR wrong number of arguments for 'expire' command\r\n"), + input: []string{"KEY1"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRE"), + }, }, "key does not exist": { - input: []string{"NONEXISTENT_KEY", strconv.FormatInt(1, 10)}, - output: clientio.RespZero, + input: []string{"NONEXISTENT_KEY", strconv.FormatInt(1, 10)}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerZero, + Error: nil, + }, + }, + "invalid expiry time - 0": { + setup: func() { + key := "EXISTING_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY", "0"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerOne, + Error: nil, + }, }, "key exists": { setup: func() { @@ -615,10 +644,13 @@ func testEvalEXPIRE(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(1, 10)}, - output: clientio.RespOne, + input: []string{"EXISTING_KEY", strconv.FormatInt(1, 10)}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerOne, + Error: nil, + }, }, - "invalid expiry time exists - very large integer": { + "invalid expiry time - very large integer": { setup: func() { key := "EXISTING_KEY" value := "mock_value" @@ -628,11 +660,14 @@ func testEvalEXPIRE(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(9223372036854776, 10)}, - output: []byte("-ERR invalid expire time in 'expire' command\r\n"), + input: []string{"EXISTING_KEY", strconv.FormatInt(9223372036854776, 10)}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIRE"), + }, }, - "invalid expiry time exists - negative integer": { + "invalid expiry time - negative integer": { setup: func() { key := "EXISTING_KEY" value := "mock_value" @@ -642,10 +677,13 @@ func testEvalEXPIRE(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(-1, 10)}, - output: []byte("-ERR invalid expire time in 'expire' command\r\n"), + input: []string{"EXISTING_KEY", strconv.FormatInt(-1, 10)}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIRE"), + }, }, - "invalid expiry time exists - empty string": { + "invalid expiry time - empty string": { setup: func() { key := "EXISTING_KEY" value := "mock_value" @@ -655,10 +693,13 @@ func testEvalEXPIRE(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", ""}, - output: []byte("-ERR value is not an integer or out of range\r\n"), + input: []string{"EXISTING_KEY", ""}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + }, }, - "invalid expiry time exists - with float number": { + "invalid expiry time - with float number": { setup: func() { key := "EXISTING_KEY" value := "mock_value" @@ -668,23 +709,32 @@ func testEvalEXPIRE(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", "0.456"}, - output: []byte("-ERR value is not an integer or out of range\r\n"), + input: []string{"EXISTING_KEY", "0.456"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + }, }, } - runEvalTests(t, tests, evalEXPIRE, store) + runMigratedEvalTests(t, tests, evalEXPIRE, store) } func testEvalEXPIRETIME(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "wrong number of args": { - input: []string{"KEY1", "KEY2"}, - output: []byte("-ERR wrong number of arguments for 'expiretime' command\r\n"), + input: []string{"KEY1", "KEY2"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRETIME"), + }, }, "key does not exist": { - input: []string{"NONEXISTENT_KEY"}, - output: clientio.RespMinusTwo, + input: []string{"NONEXISTENT_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + }, }, "key exists without expiry": { setup: func() { @@ -696,8 +746,11 @@ func testEvalEXPIRETIME(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY"}, - output: clientio.RespMinusOne, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + }, }, "key exists with expiry": { setup: func() { @@ -711,31 +764,46 @@ func testEvalEXPIRETIME(t *testing.T, store *dstore.Store) { store.SetUnixTimeExpiry(obj, 2724123456123) }, - input: []string{"EXISTING_KEY"}, - output: []byte(fmt.Sprintf(":%d\r\n", 2724123456123)), + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: uint64(2724123456123), + Error: nil, + }, }, } - runEvalTests(t, tests, evalEXPIRETIME, store) + runMigratedEvalTests(t, tests, evalEXPIRETIME, store) } func testEvalEXPIREAT(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "nil value": { - input: nil, - output: []byte("-ERR wrong number of arguments for 'expireat' command\r\n"), + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIREAT"), + }, }, "empty args": { - input: []string{}, - output: []byte("-ERR wrong number of arguments for 'expireat' command\r\n"), + input: []string{}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIREAT"), + }, }, "wrong number of args": { - input: []string{"KEY1"}, - output: []byte("-ERR wrong number of arguments for 'expireat' command\r\n"), + input: []string{"KEY1"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIREAT"), + }, }, "key does not exist": { - input: []string{"NONEXISTENT_KEY", strconv.FormatInt(time.Now().Add(2*time.Minute).Unix(), 10)}, - output: clientio.RespZero, + input: []string{"NONEXISTENT_KEY", strconv.FormatInt(time.Now().Add(2*time.Minute).Unix(), 10)}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerZero, + Error: nil, + }, }, "key exists": { setup: func() { @@ -747,8 +815,11 @@ func testEvalEXPIREAT(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(time.Now().Add(2*time.Minute).Unix(), 10)}, - output: clientio.RespOne, + input: []string{"EXISTING_KEY", strconv.FormatInt(time.Now().Add(2*time.Minute).Unix(), 10)}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerOne, + Error: nil, + }, }, "invalid expire time - very large integer": { setup: func() { @@ -760,8 +831,11 @@ func testEvalEXPIREAT(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(9223372036854776, 10)}, - output: []byte("-ERR invalid expire time in 'expireat' command\r\n"), + input: []string{"EXISTING_KEY", strconv.FormatInt(9223372036854776, 10)}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIREAT"), + }, }, "invalid expire time - negative integer": { setup: func() { @@ -773,12 +847,15 @@ func testEvalEXPIREAT(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY", strconv.FormatInt(-1, 10)}, - output: []byte("-ERR invalid expire time in 'expireat' command\r\n"), + input: []string{"EXISTING_KEY", strconv.FormatInt(-1, 10)}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIREAT"), + }, }, } - runEvalTests(t, tests, evalEXPIREAT, store) + runMigratedEvalTests(t, tests, evalEXPIREAT, store) } func testEvalJSONARRTRIM(t *testing.T, store *dstore.Store) { @@ -1155,19 +1232,19 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { setup: func() {}, input: nil, output: []byte("-ERR wrong number of arguments for 'json.arrlen' command\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongArgumentCount("JSON.ARRLEN"), - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("JSON.ARRLEN"), + }, }, "key does not exist": { setup: func() {}, input: []string{"NONEXISTENT_KEY"}, output: []byte("$-1\r\n"), - migratedOutput: EvalResponse{ - Result: clientio.NIL, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: clientio.NIL, + Error: nil, + }, }, "root not array arrlen": { setup: func() { @@ -1180,10 +1257,10 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { }, input: []string{"EXISTING_KEY"}, output: []byte("-ERR Path '$' does not exist or not an array\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongTypeOperation, - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + }, }, "root array arrlen": { setup: func() { @@ -1196,10 +1273,10 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { }, input: []string{"EXISTING_KEY"}, output: []byte(":3\r\n"), - migratedOutput: EvalResponse{ - Result: 3, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: 3, + Error: nil, + }, }, "wildcase no array arrlen": { setup: func() { @@ -1213,10 +1290,10 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "$.*"}, output: []byte("*5\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n$-1\r\n"), - migratedOutput: EvalResponse{ - Result: []interface{}{clientio.NIL,clientio.NIL,clientio.NIL,clientio.NIL,clientio.NIL}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []interface{}{clientio.NIL, clientio.NIL, clientio.NIL, clientio.NIL, clientio.NIL}, + Error: nil, + }, }, "subpath array arrlen": { setup: func() { @@ -1231,10 +1308,10 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "$.language"}, output: []byte(":2\r\n"), - migratedOutput: EvalResponse{ - Result: 2, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: 2, + Error: nil, + }, }, } for _, tt := range tests { @@ -1250,8 +1327,8 @@ func testEvalJSONARRLEN(t *testing.T, store *dstore.Store) { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { assert.Equal(t, slice, response.Result) } else { - assert.Equal(t, tt.migratedOutput.Result, response.Result) - } + assert.Equal(t, tt.migratedOutput.Result, response.Result) + } } if tt.migratedOutput.Error != nil { @@ -2070,10 +2147,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", "6"}, output: []byte("*1\r\n$-1\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrJSONPathNotFound("$.a"), - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrJSONPathNotFound("$.a"), + }, }, "arr append single element to an array field": { setup: func() { @@ -2086,10 +2163,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", "6"}, output: []byte("*1\r\n:3\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{3}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{3}, + Error: nil, + }, }, "arr append multiple elements to an array field": { setup: func() { @@ -2102,10 +2179,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", "6", "7", "8"}, output: []byte("*1\r\n:5\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{5}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{5}, + Error: nil, + }, }, "arr append string value": { setup: func() { @@ -2118,10 +2195,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.b", `"d"`}, output: []byte("*1\r\n:3\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{3}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{3}, + Error: nil, + }, }, "arr append nested array value": { setup: func() { @@ -2134,10 +2211,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", "[1,2,3]"}, output: []byte("*1\r\n:2\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{2}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{2}, + Error: nil, + }, }, "arr append with json value": { setup: func() { @@ -2150,10 +2227,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", "{\"c\": 3}"}, output: []byte("*1\r\n:2\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{2}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{2}, + Error: nil, + }, }, "arr append to append on multiple fields": { setup: func() { @@ -2166,10 +2243,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$..a", "6"}, output: []byte("*2\r\n:2\r\n:3\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{2,3}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{2, 3}, + Error: nil, + }, }, "arr append to append on root node": { setup: func() { @@ -2182,10 +2259,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$", "6"}, output: []byte("*1\r\n:4\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{4}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{4}, + Error: nil, + }, }, "arr append to an array with different type": { setup: func() { @@ -2198,10 +2275,10 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { }, input: []string{"array", "$.a", `"blue"`}, output: []byte("*1\r\n:3\r\n"), - migratedOutput: EvalResponse{ - Result: []int64{3}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []int64{3}, + Error: nil, + }, }, } for _, tt := range tests { @@ -2211,13 +2288,13 @@ func testEvalJSONARRAPPEND(t *testing.T, store *dstore.Store) { if tt.setup != nil { tt.setup() } - response := evalJSONARRAPPEND(tt.input, store) + response := evalJSONARRAPPEND(tt.input, store) if tt.migratedOutput.Result != nil { - actual, ok := response.Result.([]int64) - if ok { - assert.Equal(t, tt.migratedOutput.Result, actual) - } + actual, ok := response.Result.([]int64) + if ok { + assert.Equal(t, tt.migratedOutput.Result, actual) + } } if tt.migratedOutput.Error != nil { @@ -2318,27 +2395,131 @@ func testEvalJSONTOGGLE(t *testing.T, store *dstore.Store) { runEvalTests(t, tests, evalJSONTOGGLE, store) } +func testEvalPTTL(t *testing.T, store *dstore.Store) { + tests := map[string]evalTestCase{ + "nil value": { + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("PTTL"), + }, + }, + "empty array": { + setup: func() {}, + input: []string{}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("PTTL"), + }, + }, + "key does not exist": { + setup: func() {}, + input: []string{"NONEXISTENT_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + }, + }, + "multiple arguments": { + setup: func() {}, + input: []string{"KEY1", "KEY2"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("PTTL"), + }, + }, + "key exists expiry not set": { + setup: func() { + key := "EXISTING_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + }, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + }, + }, + "key exists not expired": { + setup: func() { + key := "EXISTING_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + + store.SetExpiry(obj, int64(2*time.Millisecond)) + }, + input: []string{"EXISTING_KEY"}, + newValidator: func(output interface{}) { + assert.True(t, output != nil) + assert.True(t, output != clientio.IntegerNegativeOne) + assert.True(t, output != clientio.IntegerNegativeTwo) + }, + }, + "key exists but expired": { + setup: func() { + key := "EXISTING_EXPIRED_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + + store.SetExpiry(obj, int64(-2*time.Millisecond)) + }, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + }, + }, + } + + runMigratedEvalTests(t, tests, evalPTTL, store) +} + func testEvalTTL(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "nil value": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'ttl' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("TTL"), + }, }, "empty array": { - setup: func() {}, - input: []string{}, - output: []byte("-ERR wrong number of arguments for 'ttl' command\r\n"), + setup: func() {}, + input: []string{}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("TTL"), + }, }, "key does not exist": { - setup: func() {}, - input: []string{"NONEXISTENT_KEY"}, - output: clientio.RespMinusTwo, + setup: func() {}, + input: []string{"NONEXISTENT_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + }, }, "multiple arguments": { - setup: func() {}, - input: []string{"KEY1", "KEY2"}, - output: []byte("-ERR wrong number of arguments for 'ttl' command\r\n"), + setup: func() {}, + input: []string{"KEY1", "KEY2"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("TTL"), + }, }, "key exists expiry not set": { setup: func() { @@ -2350,8 +2531,11 @@ func testEvalTTL(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"EXISTING_KEY"}, - output: clientio.RespMinusOne, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + }, }, "key exists not expired": { setup: func() { @@ -2366,10 +2550,10 @@ func testEvalTTL(t *testing.T, store *dstore.Store) { store.SetExpiry(obj, int64(2*time.Millisecond)) }, input: []string{"EXISTING_KEY"}, - validator: func(output []byte) { + newValidator: func(output interface{}) { assert.True(t, output != nil) - assert.True(t, !bytes.Equal(output, clientio.RespMinusOne)) - assert.True(t, !bytes.Equal(output, clientio.RespMinusTwo)) + assert.True(t, output != clientio.IntegerNegativeOne) + assert.True(t, output != clientio.IntegerNegativeTwo) }, }, "key exists but expired": { @@ -2384,12 +2568,15 @@ func testEvalTTL(t *testing.T, store *dstore.Store) { store.SetExpiry(obj, int64(-2*time.Millisecond)) }, - input: []string{"EXISTING_KEY"}, - output: clientio.RespMinusTwo, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + }, }, } - runEvalTests(t, tests, evalTTL, store) + runMigratedEvalTests(t, tests, evalTTL, store) } func testEvalDel(t *testing.T, store *dstore.Store) { @@ -4281,19 +4468,19 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { setup: func() {}, input: nil, output: []byte("-ERR wrong number of arguments for 'json.arrpop' command\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongArgumentCount("JSON.ARRPOP"), - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("JSON.ARRPOP"), + }, }, "key does not exist": { setup: func() {}, input: []string{"NOTEXISTANT_KEY"}, output: []byte("-ERR could not perform this operation on a key that doesn't exist\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrKeyNotFound, - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrKeyNotFound, + }, }, "empty array at root path": { setup: func() { @@ -4306,10 +4493,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY"}, output: []byte("-ERR Path '$' does not exist or not an array\r\n"), - migratedOutput: EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongTypeOperation, - }, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + }, }, "empty array at nested path": { setup: func() { @@ -4322,10 +4509,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$.b"}, output: []byte("*1\r\n$-1\r\n"), - migratedOutput: EvalResponse{ - Result: []interface{}{clientio.NIL}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []interface{}{clientio.NIL}, + Error: nil, + }, }, "all paths with asterix": { setup: func() { @@ -4338,10 +4525,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$.*"}, output: []byte("*2\r\n$-1\r\n$-1\r\n"), - migratedOutput: EvalResponse{ - Result: []interface{}{clientio.NIL,clientio.NIL}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []interface{}{clientio.NIL, clientio.NIL}, + Error: nil, + }, }, "array root path no index": { setup: func() { @@ -4354,10 +4541,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY"}, output: []byte(":5\r\n"), - migratedOutput: EvalResponse{ - Result: float64(5), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(5), + Error: nil, + }, }, "array root path valid positive index": { setup: func() { @@ -4370,10 +4557,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$", "2"}, output: []byte(":2\r\n"), - migratedOutput: EvalResponse{ - Result: float64(2), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(2), + Error: nil, + }, }, "array root path out of bound positive index": { setup: func() { @@ -4386,10 +4573,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$", "10"}, output: []byte(":5\r\n"), - migratedOutput: EvalResponse{ - Result: float64(5), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(5), + Error: nil, + }, }, "array root path valid negative index": { setup: func() { @@ -4402,10 +4589,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$", "-2"}, output: []byte(":4\r\n"), - migratedOutput: EvalResponse{ - Result: float64(4), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(4), + Error: nil, + }, }, "array root path out of bound negative index": { setup: func() { @@ -4418,10 +4605,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { }, input: []string{"MOCK_KEY", "$", "-10"}, output: []byte(":0\r\n"), - migratedOutput: EvalResponse{ - Result: float64(0), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(0), + Error: nil, + }, }, "array at root path updated correctly": { setup: func() { @@ -4441,10 +4628,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { equal := reflect.DeepEqual(obj.Value, want) assert.Equal(t, equal, true) }, - migratedOutput: EvalResponse{ - Result: float64(2), - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: float64(2), + Error: nil, + }, }, "nested array updated correctly": { setup: func() { @@ -4473,10 +4660,10 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { equal := reflect.DeepEqual(results[0], want) assert.Equal(t, equal, true) }, - migratedOutput: EvalResponse{ - Result: []interface{}{float64(2)}, - Error: nil, - }, + migratedOutput: EvalResponse{ + Result: []interface{}{float64(2)}, + Error: nil, + }, }, } for _, tt := range tests { @@ -4492,8 +4679,8 @@ func testEvalJSONARRPOP(t *testing.T, store *dstore.Store) { if slice, ok := tt.migratedOutput.Result.([]interface{}); ok { assert.Equal(t, slice, response.Result) } else { - assert.Equal(t, tt.migratedOutput.Result, response.Result) - } + assert.Equal(t, tt.migratedOutput.Result, response.Result) + } } if tt.migratedOutput.Error != nil { @@ -4892,7 +5079,6 @@ func testEvalJSONOBJKEYS(t *testing.T, store *dstore.Store) { } } - func BenchmarkEvalJSONOBJKEYS(b *testing.B) { sizes := []int{0, 10, 100, 1000, 10000, 100000} // Various sizes of JSON objects store := dstore.NewStore(nil, nil) @@ -5409,10 +5595,11 @@ func testEvalSETEX(t *testing.T, store *dstore.Store) { assert.Equal(t, "TEST_VALUE", getValue.Result) // Check if the TTL is set correctly (should be 5 seconds or less) - ttlValue := evalTTL([]string{"TEST_KEY"}, store) - ttl, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(string(ttlValue)), ":")) - assert.Nil(t, err, "Failed to parse TTL") - assert.True(t, ttl > 0 && ttl <= 5) + ttlResponse := evalTTL([]string{"TEST_KEY"}, store) + ttl, ok := ttlResponse.Result.(uint64) + assert.True(t, ok, "TTL result should be an uint64") + assert.True(t, ttl > 0 && ttl <= 5, "TTL should be between 0 and 5 seconds") + assert.Nil(t, ttlResponse.Error, "TTL command should not return an error") // Wait for the key to expire mockTime.SetTime(mockTime.CurrTime.Add(6 * time.Second)) @@ -5435,10 +5622,11 @@ func testEvalSETEX(t *testing.T, store *dstore.Store) { assert.Equal(t, "NEW_VALUE", getValue.Result) // Check if the TTL is set correctly - ttlValue := evalTTL([]string{"EXISTING_KEY"}, store) - ttl, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(string(ttlValue)), ":")) - assert.Nil(t, err, "Failed to parse TTL") - assert.True(t, ttl > 0 && ttl <= 10) + ttlResponse := evalTTL([]string{"EXISTING_KEY"}, store) + ttl, ok := ttlResponse.Result.(uint64) + assert.True(t, ok, "TTL result should be an uint64") + assert.True(t, ttl > 0 && ttl <= 10, "TTL should be between 0 and 10 seconds") + assert.Nil(t, ttlResponse.Error, "TTL command should not return an error") }, }, } diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index 892ed0d18..200c74e30 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -19,6 +19,151 @@ import ( "github.com/ohler55/ojg/jp" ) +// evalEXPIRE sets an expiry time(in secs) on the specified key in args +// args should contain 2 values, key and the expiry time to be set for the key +// The expiry time should be in integer format; if not, it returns encoded error response +// Returns clientio.IntegerOne if expiry was set on the key successfully. +// Once the time is lapsed, the key will be deleted automatically +func evalEXPIRE(args []string, store *dstore.Store) *EvalResponse { + if len(args) <= 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRE"), + } + } + + var key = args[0] + exDurationSec, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDurationSec < 0 || exDurationSec > maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIRE"), + } + } + + obj := store.Get(key) + + // 0 if the timeout was not set. e.g. key doesn't exist, or operation skipped due to the provided arguments + if obj == nil { + return &EvalResponse{ + Result: clientio.IntegerZero, + Error: nil, + } + } + isExpirySet, err2 := dstore.EvaluateAndSetExpiry(args[2:], utils.AddSecondsToUnixEpoch(exDurationSec), key, store) + + if isExpirySet { + return &EvalResponse{ + Result: clientio.IntegerOne, + Error: nil, + } + } else if err2 != nil { + return &EvalResponse{ + Result: nil, + Error: err2, + } + } + + return &EvalResponse{ + Result: clientio.IntegerZero, + Error: nil, + } +} + +// evalEXPIREAT sets a expiry time(in unix-time-seconds) on the specified key in args +// args should contain 2 values, key and the expiry time to be set for the key +// The expiry time should be in integer format; if not, it returns encoded error response +// Returns response.IntegerOne if expiry was set on the key successfully. +// Once the time is lapsed, the key will be deleted automatically +func evalEXPIREAT(args []string, store *dstore.Store) *EvalResponse { + if len(args) <= 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIREAT"), + } + } + + var key = args[0] + exUnixTimeSec, err := strconv.ParseInt(args[1], 10, 64) + if exUnixTimeSec < 0 || exUnixTimeSec > maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("EXPIREAT"), + } + } + + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + isExpirySet, err := dstore.EvaluateAndSetExpiry(args[2:], exUnixTimeSec, key, store) + if isExpirySet { + return &EvalResponse{ + Result: clientio.IntegerOne, + Error: nil, + } + } else if err != nil { + return &EvalResponse{ + Result: nil, + Error: err, + } + } + return &EvalResponse{ + Result: clientio.IntegerZero, + Error: nil, + } +} + +// evalEXPIRETIME returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire +// args should contain only 1 value, the key +// Returns expiration Unix timestamp in seconds. +// Returns -1 if the key exists but has no associated expiration time. +// Returns -2 if the key does not exist. +func evalEXPIRETIME(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("EXPIRETIME"), + } + } + + var key = args[0] + + obj := store.Get(key) + + // -2 if key doesn't exist + if obj == nil { + return &EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + } + } + + exTimeMili, ok := dstore.GetExpiry(obj, store) + // -1 if key doesn't have expiration time set + if !ok { + return &EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + } + } + + return &EvalResponse{ + Result: exTimeMili / 1000, + Error: nil, + } +} + // evalSET puts a new pair in db as in the args // args must contain key and value. // args can also contain multiple options - @@ -1456,6 +1601,94 @@ func evalPFMERGE(args []string, store *dstore.Store) *EvalResponse { } } +// evalPTTL returns Time-to-Live in millisecs for the queried key in args +// The key should be the only param in args else returns with an error +// Returns RESP encoded time (in secs) remaining for the key to expire +// +// RESP encoded -2 stating key doesn't exist or key is expired +// RESP encoded -1 in case no expiration is set on the key +func evalPTTL(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("PTTL"), + } + } + + key := args[0] + + obj := store.Get(key) + + if obj == nil { + return &EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + } + } + + exp, isExpirySet := dstore.GetExpiry(obj, store) + + if !isExpirySet { + return &EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + } + } + + // compute the time remaining for the key to expire and + // return the RESP encoded form of it + durationMs := exp - uint64(utils.GetCurrentTime().UnixMilli()) + return &EvalResponse{ + Result: durationMs, + Error: nil, + } +} + +// evalTTL returns Time-to-Live in secs for the queried key in args +// The key should be the only param in args else returns with an error +// Returns RESP encoded time (in secs) remaining for the key to expire +// +// RESP encoded -2 stating key doesn't exist or key is expired +// RESP encoded -1 in case no expiration is set on the key +func evalTTL(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("TTL"), + } + } + + var key = args[0] + + obj := store.Get(key) + + // if key does not exist, return RESP encoded -2 denoting key does not exist + if obj == nil { + return &EvalResponse{ + Result: clientio.IntegerNegativeTwo, + Error: nil, + } + } + + // if object exist, but no expiration is set on it then send -1 + exp, isExpirySet := dstore.GetExpiry(obj, store) + if !isExpirySet { + return &EvalResponse{ + Result: clientio.IntegerNegativeOne, + Error: nil, + } + } + + // compute the time remaining for the key to expire and + // return the RESP encoded form of it + durationMs := exp - uint64(utils.GetCurrentTime().UnixMilli()) + + return &EvalResponse{ + Result: durationMs / 1000, + Error: nil, + } +} + // Increments the number stored at field in the hash stored at key by increment. // // If key does not exist, a new key holding a hash is created. diff --git a/internal/server/cmd_meta.go b/internal/server/cmd_meta.go index c50051597..4d8626d49 100644 --- a/internal/server/cmd_meta.go +++ b/internal/server/cmd_meta.go @@ -50,6 +50,18 @@ var ( Cmd: "SET", CmdType: SingleShard, } + expireCmdMeta = CmdsMeta{ + Cmd: "EXPIRE", + CmdType: SingleShard, + } + expireAtCmdMeta = CmdsMeta{ + Cmd: "EXPIREAT", + CmdType: SingleShard, + } + expireTimeCmdMeta = CmdsMeta{ + Cmd: "EXPIRETIME", + CmdType: SingleShard, + } getCmdMeta = CmdsMeta{ Cmd: "GET", CmdType: SingleShard, @@ -134,6 +146,14 @@ var ( Cmd: "PFMERGE", CmdType: SingleShard, } + ttlCmdMeta = CmdsMeta{ + Cmd: "TTL", + CmdType: SingleShard, + } + pttlCmdMeta = CmdsMeta{ + Cmd: "PTTL", + CmdType: SingleShard, + } jsonclearCmdMeta = CmdsMeta{ Cmd: "JSON.CLEAR", @@ -273,6 +293,9 @@ func init() { // Single-shard commands. WorkerCmdsMeta["SET"] = setCmdMeta + WorkerCmdsMeta["EXPIRE"] = expireCmdMeta + WorkerCmdsMeta["EXPIREAT"] = expireAtCmdMeta + WorkerCmdsMeta["EXPIRETIME"] = expireTimeCmdMeta WorkerCmdsMeta["GET"] = getCmdMeta WorkerCmdsMeta["GETSET"] = getsetCmdMeta WorkerCmdsMeta["SETEX"] = setexCmdMeta @@ -316,6 +339,8 @@ func init() { WorkerCmdsMeta["ZPOPMIN"] = zpopminCmdMeta WorkerCmdsMeta["PFCOUNT"] = pfcountCmdMeta WorkerCmdsMeta["PFMERGE"] = pfmergeCmdMeta + WorkerCmdsMeta["TTL"] = ttlCmdMeta + WorkerCmdsMeta["PTTL"] = pttlCmdMeta WorkerCmdsMeta["HINCRBY"] = hincrbyCmdMeta WorkerCmdsMeta["HINCRBYFLOAT"] = hincrbyfloatCmdMeta WorkerCmdsMeta["HRANDFIELD"] = hrandfieldCmdMeta diff --git a/internal/store/expire.go b/internal/store/expire.go index 4f0d217a9..60181ee42 100644 --- a/internal/store/expire.go +++ b/internal/store/expire.go @@ -1,10 +1,20 @@ package store import ( + "strings" + + diceerrors "github.com/dicedb/dice/internal/errors" "github.com/dicedb/dice/internal/object" "github.com/dicedb/dice/internal/server/utils" ) +const ( + NX string = "NX" + XX string = "XX" + GT string = "GT" + LT string = "LT" +) + func hasExpired(obj *object.Obj, store *Store) bool { exp, ok := store.expires.Get(obj) if !ok { @@ -62,3 +72,77 @@ func DeleteExpiredKeys(store *Store) { } } } + +// NX: Set the expiration only if the key does not already have an expiration time. +// XX: Set the expiration only if the key already has an expiration time. +// GT: Set the expiration only if the new expiration time is greater than the current one. +// LT: Set the expiration only if the new expiration time is less than the current one. +// Returns Boolean True and error nil if expiry was set on the key successfully. +// Returns Boolean False and error nil if conditions didn't met. +// Returns Boolean False and error not-nil if invalid combination of subCommands or if subCommand is invalid +func EvaluateAndSetExpiry(subCommands []string, newExpiry int64, key string, + store *Store) (shouldSetExpiry bool, err error) { + var newExpInMilli = newExpiry * 1000 + var prevExpiry *uint64 = nil + var nxCmd, xxCmd, gtCmd, ltCmd bool + + obj := store.Get(key) + // key doesn't exist + if obj == nil { + return false, nil + } + shouldSetExpiry = true + // if no condition exists + if len(subCommands) == 0 { + store.SetUnixTimeExpiry(obj, newExpiry) + return shouldSetExpiry, nil + } + + expireTime, ok := GetExpiry(obj, store) + if ok { + prevExpiry = &expireTime + } + + for i := range subCommands { + subCommand := strings.ToUpper(subCommands[i]) + + switch subCommand { + case NX: + nxCmd = true + if prevExpiry != nil { + shouldSetExpiry = false + } + case XX: + xxCmd = true + if prevExpiry == nil { + shouldSetExpiry = false + } + case GT: + gtCmd = true + if prevExpiry == nil || *prevExpiry > uint64(newExpInMilli) { + shouldSetExpiry = false + } + case LT: + ltCmd = true + if prevExpiry != nil && *prevExpiry < uint64(newExpInMilli) { + shouldSetExpiry = false + } + default: + return false, diceerrors.ErrGeneral("Unsupported option " + subCommands[i]) + } + } + + if !nxCmd && gtCmd && ltCmd { + return false, diceerrors.ErrGeneral("GT and LT options at the same time are not compatible") + } + + if nxCmd && (xxCmd || gtCmd || ltCmd) { + return false, diceerrors.ErrGeneral("NX and XX," + + " GT or LT options at the same time are not compatible") + } + + if shouldSetExpiry { + store.SetUnixTimeExpiry(obj, newExpiry) + } + return shouldSetExpiry, nil +} diff --git a/internal/worker/cmd_meta.go b/internal/worker/cmd_meta.go index b92d38ce0..40d474fec 100644 --- a/internal/worker/cmd_meta.go +++ b/internal/worker/cmd_meta.go @@ -43,6 +43,9 @@ const ( // Single-shard commands. const ( + CmdExpire = "EXPIRE" + CmdExpireAt = "EXPIREAT" + CmdExpireTime = "EXPIRETIME" CmdSet = "SET" CmdGet = "GET" CmdGetSet = "GETSET" @@ -79,10 +82,12 @@ const ( CmdZRank = "ZRANK" CmdZCount = "ZCOUNT" CmdZRem = "ZREM" - CmdZCard = "ZCARD" + CmdZCard = "ZCARD" CmdPFAdd = "PFADD" CmdPFCount = "PFCOUNT" CmdPFMerge = "PFMERGE" + CmdTTL = "TTL" + CmdPTTL = "PTTL" CmdIncr = "INCR" CmdIncrBy = "INCRBY" CmdDecr = "DECR" @@ -148,6 +153,15 @@ var CommandsMeta = map[string]CmdMeta{ CmdSet: { CmdType: SingleShard, }, + CmdExpire: { + CmdType: SingleShard, + }, + CmdExpireAt: { + CmdType: SingleShard, + }, + CmdExpireTime: { + CmdType: SingleShard, + }, CmdGet: { CmdType: SingleShard, }, @@ -193,6 +207,12 @@ var CommandsMeta = map[string]CmdMeta{ CmdPFMerge: { CmdType: SingleShard, }, + CmdTTL: { + CmdType: SingleShard, + }, + CmdPTTL: { + CmdType: SingleShard, + }, CmdHLen: { CmdType: SingleShard, }, From 55b305f942e6a0113bd36e9a835783f8fe6d9c84 Mon Sep 17 00:00:00 2001 From: Aaditya Thakur <90773264+aadi-1024@users.noreply.github.com> Date: Sun, 3 Nov 2024 00:52:43 +0530 Subject: [PATCH 09/20] code doc fix: #1232 --- internal/worker/cmd_compose.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/worker/cmd_compose.go b/internal/worker/cmd_compose.go index c42784569..5a46c4986 100644 --- a/internal/worker/cmd_compose.go +++ b/internal/worker/cmd_compose.go @@ -33,11 +33,10 @@ func composeRename(responses ...ops.StoreResponse) interface{} { return clientio.OK } -// composeRename processes responses from multiple shards for a "Copy" operation. +// composeCopy processes responses from multiple shards for a "Copy" operation. // It iterates through all shard responses, checking for any errors. If an error is found // in any shard response, it returns that error immediately. If all responses are successful, -// it returns an "OK" response to indicate that the Rename operation succeeded across all shards. - +// it returns an "OK" response to indicate that the Copy operation succeeded across all shards. func composeCopy(responses ...ops.StoreResponse) interface{} { for idx := range responses { if responses[idx].EvalResponse.Error != nil { From 8ad878eebf94b14bfcb8ce66de47cdd1f5788462 Mon Sep 17 00:00:00 2001 From: Joseph Alikah <39875791+Ehijoe@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:18:34 +0100 Subject: [PATCH 10/20] Migrate GETEX and GETDEL commands (#1061) * migrate GETEX command to NewEval type * change return values of evalGETEX to raw types * migrate GETDEL command to NewEval type * refactor tests for evalGETEX * fix invalid type error in evalGETEX * refactor tests for evalGETDEL * fix evalGETEX error handling order * edit getdel unittests to expect lowercase command name in errors * fix error reporting in getex * create constant for PERSIST * change GETEX invalid number error and add unit tests * add resp integration tests for GETEX and GETDEL * fix GETEX and GETDEL unittests * delete async integration tests for GETEX and GETDEL * fix unexpected type error message in GETDEL and GET to report correct types * Add meta data for GETEX and GETDEL commands * fix formatting * update docs for GETDEL and GETEX --- docs/src/content/docs/commands/GETDEL.md | 41 +- docs/src/content/docs/commands/GETEX.md | 52 +- .../commands/{async => resp}/getdel_test.go | 4 +- .../commands/{async => resp}/getex_test.go | 9 +- internal/eval/commands.go | 14 +- internal/eval/constants.go | 1 + internal/eval/eval.go | 175 ----- internal/eval/eval_test.go | 126 +++- internal/eval/sortedset/sorted_set.go | 2 +- internal/eval/store_eval.go | 626 +++++++++++++----- internal/eval/type_asserts.go | 13 + internal/server/cmd_meta.go | 11 + internal/worker/cmd_meta.go | 8 + 13 files changed, 655 insertions(+), 427 deletions(-) rename integration_tests/commands/{async => resp}/getdel_test.go (97%) rename integration_tests/commands/{async => resp}/getex_test.go (96%) create mode 100644 internal/eval/type_asserts.go diff --git a/docs/src/content/docs/commands/GETDEL.md b/docs/src/content/docs/commands/GETDEL.md index 88ebf1255..1ea5b2a8b 100644 --- a/docs/src/content/docs/commands/GETDEL.md +++ b/docs/src/content/docs/commands/GETDEL.md @@ -26,17 +26,12 @@ GETDEL key ## Behaviour -When the `GETDEL` command is executed, the following steps occur: - 1. The command checks if the specified key exists in the DiceDB database. - 2. If the key exists, the value associated with the key is retrieved. - 3. The key is then deleted from the database. - 4. The retrieved value is returned to the client. - 5. If the key does not exist, `nil` is returned, and no deletion occurs. +- If the specified key exists, `GETDEL` retrieves its value and then deletes the key from the database. +- The retrieved value is returned to the client. +- If the key does not exist, `GETDEL` returns `nil` and no deletion is performed. ## Errors -The `GETDEL` command can raise errors in the following scenarios: - 1. `Wrong Type Error`: - Error Message: `ERROR WRONGTYPE Operation against a key holding the wrong kind of value` @@ -47,9 +42,11 @@ The `GETDEL` command can raise errors in the following scenarios: - Error Message: `ERROR wrong number of arguments for 'getdel' command` - Occurs if the command is called without the required parameter. -## Examples +## Example Usage + +### Retreive and Delete an Existing Key -### Example with Existent key +Setting a key `mykey` with the value `"Hello, World!"` and then using `GETDEL` to retrieve and delete it. ```bash 127.0.0.1:7379> SET mykey "Hello, World!" @@ -60,34 +57,22 @@ OK (nil) ``` -`Explanation:` +### Using `GETDEL` on a Non-Existent Key -- The key `mykey` is set with the value `"Hello, World!"`. -- The `GETDEL` command retrieves the value `"Hello, World!"` and deletes the key `mykey` from the database. -- The `GET` command attempts to retrieve the value associated with the key `mykey` and returns `nil` as the key no longer exists. - -### Example with a Non-Existent Key +Trying to retrieve and delete a key `nonexistingkey` that does not exist. ```bash 127.0.0.1:7379> GETDEL nonexistingkey (nil) ``` -`Explanation:` - -- The key `nonexistingkey` does not exist in the database. -- The `GETDEL` command returns `nil` since the key is not found. +### Using `GETDEL` on a Key with a Different Data Type -### Example with a Wrong Type of Key +Setting a key `mylist` as a list and then trying to use `GETDEL`, which is incompatible with non-string data types. ```bash 127.0.0.1:7379> LPUSH mylist "item1" (integer) 1 127.0.0.1:7379> GETDEL mylist -ERROR WRONGTYPE Operation against a key holding the wrong kind of value -``` - -`Explanation:` - -- The key `mylist` is a list, not a string. -- The `GETDEL` command raises a `WRONGTYPE` error because it expects the key to be a string. \ No newline at end of file +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` \ No newline at end of file diff --git a/docs/src/content/docs/commands/GETEX.md b/docs/src/content/docs/commands/GETEX.md index 50ad4a013..e841b3540 100644 --- a/docs/src/content/docs/commands/GETEX.md +++ b/docs/src/content/docs/commands/GETEX.md @@ -1,6 +1,6 @@ --- title: GETEX -description: Documentation for the DiceDB command GETEX +description: The `GETEX` command in DiceDB is used to retrieve the value of a specified key and simultaneously set its expiration time. This command is particularly useful when you want to access the value of a key and ensure that it expires after a certain period, all in a single atomic operation. --- The `GETEX` command in DiceDB is used to retrieve the value of a specified key and simultaneously set its expiration time. This command is particularly useful when you want to access the value of a key and ensure that it expires after a certain period, all in a single atomic operation. @@ -32,29 +32,33 @@ GETEX key [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp ## Behaviour -When the `GETEX` command is executed, it performs the following actions: +- If the specified key does not exist, `GETEX` will return nil, and no expiration time will be set. +- If the key exists, `GETEX` retrieves and returns its current value. +- When used with the `EX`, `PX`, `EXAT`, or `PXAT` options, `GETEX` will adjust the key's expiration time according to the specified option. +- Using the `PERSIST` option removes any existing expiration, making the key persist indefinitely. -1. Retrieves the value of the specified key. -2. Sets the expiration time for the key based on the provided option (`EX`, `PX`, `EXAT`, `PXAT`, or `PERSIST`). -3. Returns the value of the key. +## Errors -If the key does not exist, the command will return `nil` and no expiration time will be set. +1. `Wrong number of arguments`: -## Errors + - Error Message: `(error) ERR wrong number of arguments for 'getex' command` + - Occurs when the command is executed with an incorrect number of arguments. + +2. `Invalid expiration option`: -The `GETEX` command can raise errors in the following scenarios: + - Error Message: `(error) ERR syntax error` + - Occurs when an unrecognized or incorrect expiration option is specified. -1. `Wrong number of arguments`: If the command is not provided with the correct number of arguments, it will return an error. - - Error message: `(error) ERR wrong number of arguments for 'getex' command` -2. `Invalid expiration option`: If an invalid expiration option is provided, it will return an error. - - Error message: `(error) ERR syntax error` -3. `Invalid expiration time`: If the expiration time is not a valid integer or timestamp, it will return an error. - - Error message: `(error) ERR value is not an integer or out of range` +3. `Invalid expiration time`: + + - Error Message: `(error) ERR value is not an integer or out of range` + - Occurs when the expiration time provided is not a valid integer or timestamp. ## Example Usage -### Example 1: Retrieve value and set expiration in seconds +### Retrieve value and set expiration in seconds +This command will return `"Hello"` and set the expiration time of `mykey` to 10 seconds. ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -63,9 +67,9 @@ OK 127.0.0.1:7379> "Hello" ``` -- This command will return `"Hello"` and set the expiration time of `mykey` to 10 seconds. +### Retrieve value and set expiration in milliseconds -### Example 2: Retrieve value and set expiration in milliseconds +This command will return `"Hello"` and set the expiration time of `mykey` to 10,000 milliseconds (10 seconds). ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -74,9 +78,9 @@ OK 127.0.0.1:7379> "Hello" ``` -- This command will return `"Hello"` and set the expiration time of `mykey` to 10,000 milliseconds (10 seconds). +### Retrieve value and set expiration as Unix timestamp in seconds -### Example 3: Retrieve value and set expiration as Unix timestamp in seconds +This command will return `"Hello"` and set the expiration time of `mykey` to the Unix timestamp `1672531199`. ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -85,9 +89,9 @@ OK 127.0.0.1:7379> "Hello" ``` -- This command will return `"Hello"` and set the expiration time of `mykey` to the Unix timestamp `1672531199`. +### Retrieve value and set expiration as Unix timestamp in milliseconds -### Example 4: Retrieve value and set expiration as Unix timestamp in milliseconds +This command will return `"Hello"` and set the expiration time of `mykey` to the Unix timestamp `1672531199000` milliseconds. ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -96,9 +100,9 @@ OK 127.0.0.1:7379> "Hello" ``` -- This command will return `"Hello"` and set the expiration time of `mykey` to the Unix timestamp `1672531199000` milliseconds. +### Retrieve value and remove expiration -### Example 5: Retrieve value and remove expiration +This command will return `"Hello"` and remove the expiration time of `mykey`, making it persistent. ```bash 127.0.0.1:7379> SET mykey "Hello" @@ -109,8 +113,6 @@ OK 127.0.0.1:7379> "Hello" ``` -- This command will return `"Hello"` and remove the expiration time of `mykey`, making it persistent. - ## Notes - The `GETEX` command is atomic, meaning it ensures that the retrieval of the value and the setting of the expiration time happen as a single, indivisible operation. diff --git a/integration_tests/commands/async/getdel_test.go b/integration_tests/commands/resp/getdel_test.go similarity index 97% rename from integration_tests/commands/async/getdel_test.go rename to integration_tests/commands/resp/getdel_test.go index 476631bed..b92cf7582 100644 --- a/integration_tests/commands/async/getdel_test.go +++ b/integration_tests/commands/resp/getdel_test.go @@ -1,10 +1,10 @@ -package async +package resp import ( "testing" "time" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) func TestGetDel(t *testing.T) { diff --git a/integration_tests/commands/async/getex_test.go b/integration_tests/commands/resp/getex_test.go similarity index 96% rename from integration_tests/commands/async/getex_test.go rename to integration_tests/commands/resp/getex_test.go index 8d552fac6..94fa5434f 100644 --- a/integration_tests/commands/async/getex_test.go +++ b/integration_tests/commands/resp/getex_test.go @@ -1,11 +1,11 @@ -package async +package resp import ( "strconv" "testing" "time" - "github.com/stretchr/testify/assert" + "gotest.tools/v3/assert" ) func TestGetEx(t *testing.T) { @@ -147,7 +147,6 @@ func TestGetEx(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // deleteTestKeys([]string{"foo"}, store) FireCommand(conn, "DEL foo") for i, cmd := range tc.commands { @@ -156,9 +155,9 @@ func TestGetEx(t *testing.T) { } result := FireCommand(conn, cmd) if tc.assertType[i] == "equal" { - assert.Equal(t, tc.expected[i], result) + assert.DeepEqual(t, tc.expected[i], result) } else if tc.assertType[i] == "assert" { - assert.True(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) + assert.Assert(t, result.(int64) <= tc.expected[i].(int64), "Expected %v to be less than or equal to %v", result, tc.expected[i]) } } }) diff --git a/internal/eval/commands.go b/internal/eval/commands.go index 220eb4d54..c046926af 100644 --- a/internal/eval/commands.go +++ b/internal/eval/commands.go @@ -103,9 +103,10 @@ var ( The key should be the only param in args And If the key exists, it will be deleted before its value is returned. The RESP value of the key is encoded and then returned GETDEL returns RespNIL if key is expired or it does not exist`, - Eval: evalGETDEL, - Arity: 2, - KeySpecs: KeySpecs{BeginIndex: 1}, + Arity: 2, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, + NewEval: evalGETDEL, } msetCmdMeta = DiceCmdMeta{ Name: "MSET", @@ -678,9 +679,10 @@ var ( Name: "GETEX", Info: `Get the value of key and optionally set its expiration. GETEX is similar to GET, but is a write command with additional options.`, - Eval: evalGETEX, - Arity: -2, - KeySpecs: KeySpecs{BeginIndex: 1}, + Arity: -2, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, + NewEval: evalGETEX, } pttlCmdMeta = DiceCmdMeta{ Name: "PTTL", diff --git a/internal/eval/constants.go b/internal/eval/constants.go index d75a6e5c0..2d7a7cecc 100644 --- a/internal/eval/constants.go +++ b/internal/eval/constants.go @@ -11,6 +11,7 @@ const ( Ex string = "EX" Px string = "PX" + Persist string = "PERSIST" Pxat string = "PXAT" Exat string = "EXAT" XX string = "XX" diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 290755027..459e65434 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -204,67 +204,6 @@ func evalDBSIZE(args []string, store *dstore.Store) []byte { return clientio.Encode(store.GetDBSize(), false) } -// evalGETDEL returns the value for the queried key in args -// The key should be the only param in args -// The RESP value of the key is encoded and then returned -// In evalGETDEL If the key exists, it will be deleted before its value is returned. -// evalGETDEL returns response.RespNIL if key is expired or it does not exist -func evalGETDEL(args []string, store *dstore.Store) []byte { - if len(args) != 1 { - return diceerrors.NewErrArity("GETDEL") - } - - key := args[0] - - // getting the key based on previous touch value - obj := store.GetNoTouch(key) - - // if key does not exist, return RESP encoded nil - if obj == nil { - return clientio.RespNIL - } - - // If the object exists, check if it is a Set object. - if err := object.AssertType(obj.TypeEncoding, object.ObjTypeSet); err == nil { - return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) - } - - // If the object exists, check if it is a JSON object. - if err := object.AssertType(obj.TypeEncoding, object.ObjTypeJSON); err == nil { - return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) - } - - // Get the key from the hash table - objVal := store.GetDel(key) - - // Decode and return the value based on its encoding - switch _, oEnc := object.ExtractTypeEncoding(objVal); oEnc { - case object.ObjEncodingInt: - // Value is stored as an int64, so use type assertion - if val, ok := objVal.Value.(int64); ok { - return clientio.Encode(val, false) - } - return diceerrors.NewErrWithFormattedMessage("expected int64 but got another type: %s", objVal.Value) - - case object.ObjEncodingEmbStr, object.ObjEncodingRaw: - // Value is stored as a string, use type assertion - if val, ok := objVal.Value.(string); ok { - return clientio.Encode(val, false) - } - return diceerrors.NewErrWithMessage("expected string but got another type") - - case object.ObjEncodingByteArray: - // Value is stored as a bytearray, use type assertion - if val, ok := objVal.Value.(*ByteArray); ok { - return clientio.Encode(string(val.data), false) - } - return diceerrors.NewErrWithMessage(diceerrors.WrongTypeErr) - - default: - return diceerrors.NewErrWithMessage(diceerrors.WrongTypeErr) - } -} - // evalJSONDEBUG reports value's memory usage in bytes // Returns arity error if subcommand is missing // Supports only two subcommand as of now - HELP and MEMORY @@ -1989,120 +1928,6 @@ func evalCOPY(args []string, store *dstore.Store) []byte { return clientio.RespOne } -// GETEX key [EX seconds | PX milliseconds | EXAT unix-time-seconds | -// PXAT unix-time-milliseconds | PERSIST] -// Get the value of key and optionally set its expiration. -// GETEX is similar to GET, but is a write command with additional options. -// The GETEX command supports a set of options that modify its behavior: -// EX seconds -- Set the specified expire time, in seconds. -// PX milliseconds -- Set the specified expire time, in milliseconds. -// EXAT timestamp-seconds -- Set the specified Unix time at which the key will expire, in seconds. -// PXAT timestamp-milliseconds -- Set the specified Unix time at which the key will expire, in milliseconds. -// PERSIST -- Remove the time to live associated with the key. -// The RESP value of the key is encoded and then returned -// evalGET returns response.RespNIL if key is expired or it does not exist -func evalGETEX(args []string, store *dstore.Store) []byte { - if len(args) < 1 { - return diceerrors.NewErrArity("GETEX") - } - - key := args[0] - - // Get the key from the hash table - obj := store.Get(key) - - // if key does not exist, return RESP encoded nil - if obj == nil { - return clientio.RespNIL - } - - // check if the object is set type or json type if yes then return error - if object.AssertType(obj.TypeEncoding, object.ObjTypeSet) == nil || - object.AssertType(obj.TypeEncoding, object.ObjTypeJSON) == nil { - return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) - } - - var exDurationMs int64 = -1 - state := Uninitialized - persist := false - for i := 1; i < len(args); i++ { - arg := strings.ToUpper(args[i]) - switch arg { - case Ex, Px: - if state != Uninitialized { - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - i++ - if i == len(args) { - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return diceerrors.NewErrWithMessage(diceerrors.IntOrOutOfRangeErr) - } - if exDuration <= 0 || exDuration > maxExDuration { - return diceerrors.NewErrExpireTime("GETEX") - } - - // converting seconds to milliseconds - if arg == Ex { - exDuration *= 1000 - } - exDurationMs = exDuration - state = Initialized - - case Pxat, Exat: - if state != Uninitialized { - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - i++ - if i == len(args) { - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return diceerrors.NewErrWithMessage(diceerrors.IntOrOutOfRangeErr) - } - - if exDuration < 0 || exDuration > maxExDuration { - return diceerrors.NewErrExpireTime("GETEX") - } - - if arg == Exat { - exDuration *= 1000 - } - exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() - // If the expiry time is in the past, set exDurationMs to 0 - // This will be used to signal immediate expiration - if exDurationMs < 0 { - exDurationMs = 0 - } - state = Initialized - - case "PERSIST": - if state != Uninitialized { - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - persist = true - state = Initialized - default: - return diceerrors.NewErrWithMessage(diceerrors.SyntaxErr) - } - } - - if state == Initialized { - if persist { - dstore.DelExpiry(obj, store) - } else { - store.SetExpiry(obj, exDurationMs) - } - } - - // return the RESP encoded value - return clientio.Encode(obj.Value, false) -} - // evalHSET sets the specified fields to their // respective values in a hashmap stored at key // diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 29b8a43ee..82c0346e8 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -91,7 +91,7 @@ func TestEval(t *testing.T) { testEvalHLEN(t, store) testEvalSELECT(t, store) testEvalLLEN(t, store) - testEvalGETEX(t, store) + testEvalGETDEL(t, store) testEvalJSONNUMINCRBY(t, store) testEvalDUMP(t, store) testEvalTYPE(t, store) @@ -387,8 +387,11 @@ func testEvalGETEX(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"foo", Ex, "10"}, - output: clientio.Encode("bar", false), + input: []string{"foo", Ex, "10"}, + migratedOutput: EvalResponse{ + Result: "bar", + Error: nil, + }, }, "key val pair and invalid EX": { setup: func() { @@ -399,26 +402,129 @@ func testEvalGETEX(t *testing.T, store *dstore.Store) { } store.Put(key, obj) }, - input: []string{"foo", Ex, "10000000000000000"}, - output: []byte("-ERR invalid expire time in 'getex' command\r\n"), + input: []string{"foo", Ex, "10000000000000000"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("GETEX"), + }, + }, + "key val pair and EX and string expire time": { + setup: func() { + key := "foo" + value := "bar" + obj := &object.Obj{ + Value: value, + } + store.Put(key, obj) + }, + input: []string{"foo", Ex, "string"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + }, + }, + "key val pair and both EX and PERSIST": { + setup: func() { + key := "foo" + value := "bar" + obj := &object.Obj{ + Value: value, + } + store.Put(key, obj) + }, + input: []string{"foo", Ex, Persist}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + }, }, "key holding json type": { setup: func() { evalJSONSET([]string{"JSONKEY", "$", "1"}, store) }, - input: []string{"JSONKEY"}, - output: []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"), + input: []string{"JSONKEY"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + }, }, "key holding set type": { setup: func() { evalSADD([]string{"SETKEY", "FRUITS", "APPLE", "MANGO", "BANANA"}, store) }, - input: []string{"SETKEY"}, - output: []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"), + input: []string{"SETKEY"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + }, + }, + } + + runMigratedEvalTests(t, tests, evalGETEX, store) +} + +func testEvalGETDEL(t *testing.T, store *dstore.Store) { + tests := map[string]evalTestCase{ + "nil value": { + input: nil, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR wrong number of arguments for 'getdel' command")}, + }, + "empty array": { + input: []string{}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR wrong number of arguments for 'getdel' command")}, + }, + "key does not exist": { + input: []string{"NONEXISTENT_KEY"}, + migratedOutput: EvalResponse{Result: clientio.NIL, Error: nil}, + }, + "multiple arguments": { + input: []string{"KEY1", "KEY2"}, + migratedOutput: EvalResponse{Result: nil, Error: errors.New("ERR wrong number of arguments for 'getdel' command")}, + }, + "key exists": { + setup: func() { + key := "diceKey" + value := "diceVal" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + }, + input: []string{"diceKey"}, + migratedOutput: EvalResponse{Result: "diceVal", Error: nil}, + }, + "key exists but expired": { + setup: func() { + key := "EXISTING_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + store.SetExpiry(obj, int64(-2*time.Millisecond)) + }, + input: []string{"EXISTING_KEY"}, + migratedOutput: EvalResponse{Result: clientio.NIL, Error: nil}, + }, + "key deleted by previous call of GETDEL": { + setup: func() { + key := "DELETED_KEY" + value := "mock_value" + obj := &object.Obj{ + Value: value, + LastAccessedAt: uint32(time.Now().Unix()), + } + store.Put(key, obj) + evalGETDEL([]string{key}, store) + }, + input: []string{"DELETED_KEY"}, + migratedOutput: EvalResponse{Result: clientio.NIL, Error: nil}, }, } - runEvalTests(t, tests, evalGETEX, store) + runMigratedEvalTests(t, tests, evalGETDEL, store) } func testEvalMSET(t *testing.T, store *dstore.Store) { diff --git a/internal/eval/sortedset/sorted_set.go b/internal/eval/sortedset/sorted_set.go index 5cb390514..f24a62a93 100644 --- a/internal/eval/sortedset/sorted_set.go +++ b/internal/eval/sortedset/sorted_set.go @@ -206,7 +206,7 @@ func (ss *Set) Get(member string) (float64, bool) { func (ss *Set) Len() int { cardinality := len(ss.memberMap) return cardinality - } +} // This func is used to remove the maximum element from the sortedset. // It takes count as an argument which tells the number of elements to be removed from the sortedset. diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index 200c74e30..d69e460ce 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -181,169 +181,169 @@ func evalEXPIRETIME(args []string, store *dstore.Store) *EvalResponse { // Returns encoded OK RESP once new entry is added // If the key already exists then the value will be overwritten and expiry will be discarded func evalSET(args []string, store *dstore.Store) *EvalResponse { - if len(args) <= 1 { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrWrongArgumentCount("SET"), - } - } - - var key, value string - var exDurationMs int64 = -1 - var state exDurationState = Uninitialized - var keepttl bool = false - + if len(args) <= 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("SET"), + } + } + + var key, value string + var exDurationMs int64 = -1 + var state exDurationState = Uninitialized + var keepttl bool = false + key, value = args[0], args[1] - oType, oEnc := deduceTypeEncoding(value) - - for i := 2; i < len(args); i++ { - arg := strings.ToUpper(args[i]) - switch arg { - case Ex, Px: - if state != Uninitialized { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - if keepttl { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - i++ - if i == len(args) { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrIntegerOutOfRange, - } - } - - if exDuration <= 0 || exDuration >= maxExDuration { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrInvalidExpireTime("SET"), - } - } - - // converting seconds to milliseconds - if arg == Ex { - exDuration *= 1000 - } - exDurationMs = exDuration - state = Initialized - - case Pxat, Exat: - if state != Uninitialized { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - if keepttl { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - i++ - if i == len(args) { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - exDuration, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrIntegerOutOfRange, - } - } - - if exDuration < 0 { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrInvalidExpireTime("SET"), - } - } - - if arg == Exat { - exDuration *= 1000 - } - exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() - // If the expiry time is in the past, set exDurationMs to 0 - // This will be used to signal immediate expiration - if exDurationMs < 0 { - exDurationMs = 0 - } - state = Initialized - - case XX: - // Get the key from the hash table - obj := store.Get(key) - - // if key does not exist, return RESP encoded nil - if obj == nil { - return &EvalResponse{ - Result: clientio.NIL, - Error: nil, - } - } - case NX: - obj := store.Get(key) - if obj != nil { - return &EvalResponse{ - Result: clientio.NIL, - Error: nil, - } - } - case KeepTTL: - if state != Uninitialized { - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - keepttl = true - default: - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrSyntax, - } - } - } - - // Cast the value properly based on the encoding type - var storedValue interface{} - switch oEnc { - case object.ObjEncodingInt: - storedValue, _ = strconv.ParseInt(value, 10, 64) - case object.ObjEncodingEmbStr, object.ObjEncodingRaw: - storedValue = value - default: - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrUnsupportedEncoding(int(oEnc)), - } - } - - // putting the k and value in a Hash Table - store.Put(key, store.NewObj(storedValue, exDurationMs, oType, oEnc), dstore.WithKeepTTL(keepttl)) - - return &EvalResponse{ - Result: clientio.OK, - Error: nil, - } + oType, oEnc := deduceTypeEncoding(value) + + for i := 2; i < len(args); i++ { + arg := strings.ToUpper(args[i]) + switch arg { + case Ex, Px: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + if keepttl { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDuration <= 0 || exDuration >= maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("SET"), + } + } + + // converting seconds to milliseconds + if arg == Ex { + exDuration *= 1000 + } + exDurationMs = exDuration + state = Initialized + + case Pxat, Exat: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + if keepttl { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDuration < 0 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("SET"), + } + } + + if arg == Exat { + exDuration *= 1000 + } + exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() + // If the expiry time is in the past, set exDurationMs to 0 + // This will be used to signal immediate expiration + if exDurationMs < 0 { + exDurationMs = 0 + } + state = Initialized + + case XX: + // Get the key from the hash table + obj := store.Get(key) + + // if key does not exist, return RESP encoded nil + if obj == nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + case NX: + obj := store.Get(key) + if obj != nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + case KeepTTL: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + keepttl = true + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + } + + // Cast the value properly based on the encoding type + var storedValue interface{} + switch oEnc { + case object.ObjEncodingInt: + storedValue, _ = strconv.ParseInt(value, 10, 64) + case object.ObjEncodingEmbStr, object.ObjEncodingRaw: + storedValue = value + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnsupportedEncoding(int(oEnc)), + } + } + + // putting the k and value in a Hash Table + store.Put(key, store.NewObj(storedValue, exDurationMs, oType, oEnc), dstore.WithKeepTTL(keepttl)) + + return &EvalResponse{ + Result: clientio.OK, + Error: nil, + } } // evalGET returns the value for the queried key in args @@ -374,29 +374,40 @@ func evalGET(args []string, store *dstore.Store) *EvalResponse { switch _, oEnc := object.ExtractTypeEncoding(obj); oEnc { case object.ObjEncodingInt: // Value is stored as an int64, so use type assertion - if val, ok := obj.Value.(int64); ok { + if IsInt64(obj.Value) { return &EvalResponse{ - Result: val, + Result: obj.Value, Error: nil, } - } - - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrUnexpectedType("int64", obj.Value), + } else if IsString(obj.Value) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("int64", "string"), + } + } else { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("int64", "unknown"), + } } case object.ObjEncodingEmbStr, object.ObjEncodingRaw: // Value is stored as a string, use type assertion - if val, ok := obj.Value.(string); ok { + if IsString(obj.Value) { return &EvalResponse{ - Result: val, + Result: obj.Value, Error: nil, } - } - return &EvalResponse{ - Result: nil, - Error: diceerrors.ErrUnexpectedType("string", obj.Value), + } else if IsInt64(obj.Value) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("string", "int64"), + } + } else { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("string", "unknown"), + } } case object.ObjEncodingByteArray: @@ -3206,3 +3217,268 @@ func evalJSONOBJKEYS(args []string, store *dstore.Store) *EvalResponse { Error: nil, } } + +// GETEX key [EX seconds | PX milliseconds | EXAT unix-time-seconds | +// PXAT unix-time-milliseconds | PERSIST] +// Get the value of key and optionally set its expiration. +// GETEX is similar to GET, but is a write command with additional options. +// The GETEX command supports a set of options that modify its behavior: +// EX seconds -- Set the specified expire time, in seconds. +// PX milliseconds -- Set the specified expire time, in milliseconds. +// EXAT timestamp-seconds -- Set the specified Unix time at which the key will expire, in seconds. +// PXAT timestamp-milliseconds -- Set the specified Unix time at which the key will expire, in milliseconds. +// PERSIST -- Remove the time to live associated with the key. +// The RESP value of the key is encoded and then returned +// evalGET returns response.RespNIL if key is expired or it does not exist +func evalGETEX(args []string, store *dstore.Store) *EvalResponse { + if len(args) < 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("GETEX"), + } + } + + var key = args[0] + + var exDurationMs int64 = -1 + var state = Uninitialized + var persist = false + for i := 1; i < len(args); i++ { + arg := strings.ToUpper(args[i]) + switch arg { + case Ex, Px: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + if exDuration <= 0 || exDuration > maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("GETEX"), + } + } + + // converting seconds to milliseconds + if arg == Ex { + exDuration *= 1000 + } + exDurationMs = exDuration + state = Initialized + + case Pxat, Exat: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + i++ + if i == len(args) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + exDuration, err := strconv.ParseInt(args[i], 10, 64) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + + if exDuration < 0 || exDuration > maxExDuration { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrInvalidExpireTime("GETEX"), + } + } + + if arg == Exat { + exDuration *= 1000 + } + exDurationMs = exDuration - utils.GetCurrentTime().UnixMilli() + // If the expiry time is in the past, set exDurationMs to 0 + // This will be used to signal immediate expiration + if exDurationMs < 0 { + exDurationMs = 0 + } + state = Initialized + + case Persist: + if state != Uninitialized { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrSyntax, + } + } + persist = true + state = Initialized + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIntegerOutOfRange, + } + } + } + + // Get the key from the hash table + obj := store.Get(key) + + if obj == nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + + if object.AssertType(obj.TypeEncoding, object.ObjTypeSet) == nil || + object.AssertType(obj.TypeEncoding, object.ObjTypeJSON) == nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } + + // Get EvalResponse with correct data type + getResp := evalGET([]string{key}, store) + + // If there is an error return the error response + if getResp.Error != nil { + return getResp + } + + if state == Initialized { + if persist { + dstore.DelExpiry(obj, store) + } else { + store.SetExpiry(obj, exDurationMs) + } + } + + // return an EvalResponse with the value + return getResp +} + +// evalGETDEL returns the value for the queried key in args +// The key should be the only param in args +// The RESP value of the key is encoded and then returned +// In evalGETDEL If the key exists, it will be deleted before its value is returned. +// evalGETDEL returns response.RespNIL if key is expired or it does not exist +func evalGETDEL(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 1 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("GETDEL"), + } + } + + key := args[0] + + // getting the key based on previous touch value + obj := store.GetNoTouch(key) + + // if key does not exist, return RESP encoded nil + if obj == nil { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + + // If the object exists, check if it is a Set object. + if err := object.AssertType(obj.TypeEncoding, object.ObjTypeSet); err == nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } + + // If the object exists, check if it is a JSON object. + if err := object.AssertType(obj.TypeEncoding, object.ObjTypeJSON); err == nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } + + // Get the key from the hash table + objVal := store.GetDel(key) + + // Decode and return the value based on its encoding + switch _, oEnc := object.ExtractTypeEncoding(objVal); oEnc { + case object.ObjEncodingInt: + // Value is stored as an int64, so use type assertion + if IsInt64(objVal.Value) { + return &EvalResponse{ + Result: objVal.Value, + Error: nil, + } + } else if IsString(objVal.Value) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("int64", "string"), + } + } else { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("int64", "unknown"), + } + } + + case object.ObjEncodingEmbStr, object.ObjEncodingRaw: + // Value is stored as a string, use type assertion + if IsString(objVal.Value) { + return &EvalResponse{ + Result: objVal.Value, + Error: nil, + } + } else if IsInt64(objVal.Value) { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("string", "int64"), + } + } else { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrUnexpectedType("string", "unknown"), + } + } + + case object.ObjEncodingByteArray: + // Value is stored as a bytearray, use type assertion + if val, ok := objVal.Value.(*ByteArray); ok { + return &EvalResponse{ + Result: string(val.data), + Error: nil, + } + } + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + + default: + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } +} diff --git a/internal/eval/type_asserts.go b/internal/eval/type_asserts.go new file mode 100644 index 000000000..ef159ffba --- /dev/null +++ b/internal/eval/type_asserts.go @@ -0,0 +1,13 @@ +package eval + +// IsInt64 checks if the variable is of type int64. +func IsInt64(v interface{}) bool { + _, ok := v.(int64) + return ok +} + +// IsString checks if the variable is of type string. +func IsString(v interface{}) bool { + _, ok := v.(string) + return ok +} diff --git a/internal/server/cmd_meta.go b/internal/server/cmd_meta.go index 4d8626d49..254d279d5 100644 --- a/internal/server/cmd_meta.go +++ b/internal/server/cmd_meta.go @@ -279,6 +279,14 @@ var ( Cmd: "CMS.MERGE", CmdType: SingleShard, } + getexCmdMeta = CmdsMeta{ + Cmd: "GETEX", + CmdType: SingleShard, + } + getdelCmdMeta = CmdsMeta{ + Cmd: "GETDEL", + CmdType: SingleShard, + } // Metadata for multishard commands would go here. // These commands require both breakup and gather logic. @@ -361,5 +369,8 @@ func init() { WorkerCmdsMeta["CMS.INCRBY"] = cmsIncrByCmdMeta WorkerCmdsMeta["CMS.QUERY"] = cmsQueryCmdMeta WorkerCmdsMeta["CMS.MERGE"] = cmsMergeCmdMeta + WorkerCmdsMeta["GETEX"] = getexCmdMeta + WorkerCmdsMeta["GETDEL"] = getdelCmdMeta + // Additional commands (multishard, custom) can be added here as needed. } diff --git a/internal/worker/cmd_meta.go b/internal/worker/cmd_meta.go index 40d474fec..65dc3b792 100644 --- a/internal/worker/cmd_meta.go +++ b/internal/worker/cmd_meta.go @@ -49,6 +49,8 @@ const ( CmdSet = "SET" CmdGet = "GET" CmdGetSet = "GETSET" + CmdGetEx = "GETEX" + CmdGetDel = "GETDEL" CmdJSONArrAppend = "JSON.ARRAPPEND" CmdJSONArrLen = "JSON.ARRLEN" CmdJSONArrPop = "JSON.ARRPOP" @@ -168,6 +170,12 @@ var CommandsMeta = map[string]CmdMeta{ CmdGetSet: { CmdType: SingleShard, }, + CmdGetEx: { + CmdType: SingleShard, + }, + CmdGetDel: { + CmdType: SingleShard, + }, CmdHExists: { CmdType: SingleShard, }, From a205b488136ad9ad9b20ec1f0bd9de7ca42971b5 Mon Sep 17 00:00:00 2001 From: Apoorv Yadav <32174554+apoorvyadav1111@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:50:00 +0530 Subject: [PATCH 11/20] moved hexists, hkeys, hvals in commands (#1229) --- .../content/docs/commands/HEXISTS.md} | 0 .../{hkeys_command_docs.md => src/content/docs/commands/HKEYS.md} | 0 .../{hvals_command_docs.md => src/content/docs/commands/HVALS.md} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename docs/{hexists_command_docs.md => src/content/docs/commands/HEXISTS.md} (100%) rename docs/{hkeys_command_docs.md => src/content/docs/commands/HKEYS.md} (100%) rename docs/{hvals_command_docs.md => src/content/docs/commands/HVALS.md} (100%) diff --git a/docs/hexists_command_docs.md b/docs/src/content/docs/commands/HEXISTS.md similarity index 100% rename from docs/hexists_command_docs.md rename to docs/src/content/docs/commands/HEXISTS.md diff --git a/docs/hkeys_command_docs.md b/docs/src/content/docs/commands/HKEYS.md similarity index 100% rename from docs/hkeys_command_docs.md rename to docs/src/content/docs/commands/HKEYS.md diff --git a/docs/hvals_command_docs.md b/docs/src/content/docs/commands/HVALS.md similarity index 100% rename from docs/hvals_command_docs.md rename to docs/src/content/docs/commands/HVALS.md From 680b83c04d5a7de1b4005b635299f0396e9581d9 Mon Sep 17 00:00:00 2001 From: psr Date: Mon, 4 Nov 2024 18:07:26 +0530 Subject: [PATCH 12/20] #998: Added GET.UNWATCH command support and fix watch related issues (#1201) --- go.mod | 2 +- go.sum | 4 +- .../commands/resp/getunwatch_test.go | 181 ++++++++++++++++++ internal/cmd/cmds.go | 6 +- internal/errors/migrated_errors.go | 1 + internal/watchmanager/watch_manager.go | 4 +- internal/worker/cmd_meta.go | 10 + internal/worker/worker.go | 41 ++++ main.go | 2 +- 9 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 integration_tests/commands/resp/getunwatch_test.go diff --git a/go.mod b/go.mod index 145a914ac..8d93328fb 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 github.com/cockroachdb/swiss v0.0.0-20240612210725-f4de07ae6964 github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da - github.com/dicedb/dicedb-go v0.0.0-20241015181607-d31c1df12107 + github.com/dicedb/dicedb-go v0.0.0-20241026093718-570de4575be3 github.com/gobwas/glob v0.2.3 github.com/google/btree v1.1.3 github.com/google/go-cmp v0.6.0 diff --git a/go.sum b/go.sum index 662ea7137..aefcf136c 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFP github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dicedb/dicedb-go v0.0.0-20241015181607-d31c1df12107 h1:sL5dXtCsogSMP/afS2K2vVMMYFqJy02EezeRXpZnGy0= -github.com/dicedb/dicedb-go v0.0.0-20241015181607-d31c1df12107/go.mod h1:iaOsphlvjJ87VL/5d32ZgeQYxhYS51k7/bvFKro7lWk= +github.com/dicedb/dicedb-go v0.0.0-20241026093718-570de4575be3 h1:JvnAibMNGA0vQH+T47Y/d5/POURIvfJl3fFk0GIEBkQ= +github.com/dicedb/dicedb-go v0.0.0-20241026093718-570de4575be3/go.mod h1:p7x5/3S6wBEmiRMwxavj1I1P1xsSVQS6fcSbeai5ic4= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= diff --git a/integration_tests/commands/resp/getunwatch_test.go b/integration_tests/commands/resp/getunwatch_test.go new file mode 100644 index 000000000..542260974 --- /dev/null +++ b/integration_tests/commands/resp/getunwatch_test.go @@ -0,0 +1,181 @@ +package resp + +import ( + "context" + "fmt" + "net" + "testing" + "time" + + "github.com/dicedb/dice/internal/clientio" + "github.com/dicedb/dicedb-go" + "github.com/stretchr/testify/assert" +) + +const ( + getUnwatchKey = "getunwatchkey" + fingerprint = "3557732805" +) + +type getUnwatchTestCase struct { + key string + val string +} + +var getUnwatchTestCases = []getUnwatchTestCase{ + {getUnwatchKey, "value1"}, + {getUnwatchKey, "value2"}, + {getUnwatchKey, "value3"}, + {getUnwatchKey, "value4"}, +} + +func TestGETUNWATCH(t *testing.T) { + publisher := getLocalConnection() + subscribers := []net.Conn{getLocalConnection(), getLocalConnection(), getLocalConnection()} + + defer func() { + if err := publisher.Close(); err != nil { + t.Errorf("Error closing publisher connection: %v", err) + } + for _, sub := range subscribers { + if err := sub.Close(); err != nil { + t.Errorf("Error closing subscriber connection: %v", err) + } + } + }() + + FireCommand(publisher, fmt.Sprintf("DEL %s", getUnwatchKey)) + + // Fire SET command to set the key + res := FireCommand(publisher, fmt.Sprintf("SET %s %s", getUnwatchKey, "value")) + assert.Equal(t, "OK", res) + + // subscribe for updates + respParsers := make([]*clientio.RESPParser, len(subscribers)) + for i, subscriber := range subscribers { + rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("GET.WATCH %s", getUnwatchKey)) + assert.NotNil(t, rp) + respParsers[i] = rp + + v, err := rp.DecodeOne() + assert.Nil(t, err) + castedValue, ok := v.([]interface{}) + if !ok { + t.Errorf("Type assertion to []interface{} failed for value: %v", v) + } + assert.Equal(t, 3, len(castedValue)) + } + + // Fire updates to the key using the publisher, then check if the subscribers receive the updates in the push-response form (i.e. array of three elements, with third element being the value) + for _, tc := range getUnwatchTestCases { + res := FireCommand(publisher, fmt.Sprintf("SET %s %s", tc.key, tc.val)) + assert.Equal(t, "OK", res) + + for _, rp := range respParsers { + v, err := rp.DecodeOne() + assert.Nil(t, err) + castedValue, ok := v.([]interface{}) + if !ok { + t.Errorf("Type assertion to []interface{} failed for value: %v", v) + } + assert.Equal(t, 3, len(castedValue)) + assert.Equal(t, "GET", castedValue[0]) + assert.Equal(t, fingerprint, castedValue[1]) + assert.Equal(t, tc.val, castedValue[2]) + } + } + + // unsubscribe from updates + for _, subscriber := range subscribers { + rp := fireCommandAndGetRESPParser(subscriber, fmt.Sprintf("GET.UNWATCH %s", fingerprint)) + assert.NotNil(t, rp) + + v, err := rp.DecodeOne() + assert.NoError(t, err) + castedValue, ok := v.(string) + if !ok { + t.Errorf("Type assertion to string failed for value: %v", v) + } + assert.Equal(t, castedValue, "OK") + } + + // Test updates are not sent after unsubscribing + for _, tc := range getUnwatchTestCases[2:] { + res := FireCommand(publisher, fmt.Sprintf("SET %s %s", tc.key, tc.val)) + assert.Equal(t, "OK", res) + + for _, rp := range respParsers { + responseChan := make(chan interface{}) + errChan := make(chan error) + + go func() { + v, err := rp.DecodeOne() + if err != nil { + errChan <- err + } else { + responseChan <- v + } + }() + + select { + case v := <-responseChan: + t.Errorf("Unexpected response after unwatch: %v", v) + case err := <-errChan: + t.Errorf("Error while decoding: %v", err) + case <-time.After(100 * time.Millisecond): + // This is the expected behavior - no response within the timeout + } + } + } +} + +func TestGETUNWATCHWithSDK(t *testing.T) { + publisher := getLocalSdk() + subscribers := []WatchSubscriber{{client: getLocalSdk()}, {client: getLocalSdk()}, {client: getLocalSdk()}} + + publisher.Del(context.Background(), getUnwatchKey) + + // subscribe for updates + channels := make([]<-chan *dicedb.WatchResult, len(subscribers)) + for i, subscriber := range subscribers { + watch := subscriber.client.WatchConn(context.Background()) + subscribers[i].watch = watch + assert.NotNil(t, watch) + firstMsg, err := watch.Watch(context.Background(), "GET", getUnwatchKey) + assert.Nil(t, err) + assert.Equal(t, firstMsg.Command, "GET") + assert.Equal(t, firstMsg.Fingerprint, fingerprint) + channels[i] = watch.Channel() + } + + // Fire updates and validate receipt + err := publisher.Set(context.Background(), getUnwatchKey, "check", 0).Err() + assert.Nil(t, err) + + for _, channel := range channels { + v := <-channel + assert.Equal(t, "GET", v.Command) // command + assert.Equal(t, fingerprint, v.Fingerprint) // Fingerprint + assert.Equal(t, "check", v.Data.(string)) // data + } + + // unsubscribe from updates + for _, subscriber := range subscribers { + err := subscriber.watch.Unwatch(context.Background(), "GET", fingerprint) + assert.Nil(t, err) + } + + // fire updates and validate that they are not received + err = publisher.Set(context.Background(), getUnwatchKey, "final", 0).Err() + assert.Nil(t, err) + for _, channel := range channels { + go func(ch <-chan *dicedb.WatchResult) { + select { + case v := <-ch: + assert.Fail(t, fmt.Sprintf("%v", v)) + case <-time.After(100 * time.Millisecond): + // This is the expected behavior - no response within the timeout + } + }(channel) + } +} diff --git a/internal/cmd/cmds.go b/internal/cmd/cmds.go index 82270029e..68143983a 100644 --- a/internal/cmd/cmds.go +++ b/internal/cmd/cmds.go @@ -28,5 +28,9 @@ func (cmd *DiceDBCmd) GetFingerprint() uint32 { // This is not true for all commands, however, for now this is only used by the watch manager, // which as of now only supports a small subset of commands (all of which fit this implementation). func (cmd *DiceDBCmd) GetKey() string { - return cmd.Args[0] + var c string + if len(cmd.Args) > 0 { + c = cmd.Args[0] + } + return c } diff --git a/internal/errors/migrated_errors.go b/internal/errors/migrated_errors.go index d9a8dc864..60630f252 100644 --- a/internal/errors/migrated_errors.go +++ b/internal/errors/migrated_errors.go @@ -31,6 +31,7 @@ var ( ErrAborted = errors.New("server received ABORT command") ErrEmptyCommand = errors.New("empty command") ErrInvalidIPAddress = errors.New("invalid IP address") + ErrInvalidFingerprint = errors.New("invalid fingerprint") // Error generation functions for specific error messages with dynamic parameters. ErrWrongArgumentCount = func(command string) error { diff --git a/internal/watchmanager/watch_manager.go b/internal/watchmanager/watch_manager.go index 5dae688cd..12451c654 100644 --- a/internal/watchmanager/watch_manager.go +++ b/internal/watchmanager/watch_manager.go @@ -107,8 +107,6 @@ func (m *Manager) handleUnsubscription(sub WatchSubscription) { if len(clients) == 0 { // Remove the fingerprint from tcpSubscriptionMap delete(m.tcpSubscriptionMap, fingerprint) - // Also remove the fingerprint from fingerprintCmdMap - delete(m.fingerprintCmdMap, fingerprint) } } @@ -123,6 +121,8 @@ func (m *Manager) handleUnsubscription(sub WatchSubscription) { delete(m.querySubscriptionMap, key) } } + // Also remove the fingerprint from fingerprintCmdMap + delete(m.fingerprintCmdMap, fingerprint) } } diff --git a/internal/worker/cmd_meta.go b/internal/worker/cmd_meta.go index 65dc3b792..4df6b2392 100644 --- a/internal/worker/cmd_meta.go +++ b/internal/worker/cmd_meta.go @@ -32,6 +32,10 @@ const ( // Watch represents a command that is used to monitor changes or events. // This type of command listens for changes on specific keys or resources and responds accordingly. Watch + + // Unwatch represents a command that is used to stop monitoring changes or events. + // This type of command stops listening for changes on specific keys or resources. + Unwatch ) // Global commands @@ -71,6 +75,7 @@ const ( // Watch commands const ( CmdGetWatch = "GET.WATCH" + CmdGetUnWatch = "GET.UNWATCH" CmdZRangeWatch = "ZRANGE.WATCH" CmdHExists = "HEXISTS" CmdHKeys = "HKEYS" @@ -303,6 +308,11 @@ var CommandsMeta = map[string]CmdMeta{ CmdType: Watch, }, + // Unwatch commands + CmdGetUnWatch: { + CmdType: Unwatch, + }, + // Sorted set commands CmdZAdd: { CmdType: SingleShard, diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 67f127001..304d3aced 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -8,6 +8,7 @@ import ( "fmt" "log/slog" "net" + "strconv" "sync/atomic" "syscall" "time" @@ -248,7 +249,47 @@ func (w *BaseWorker) executeCommand(ctx context.Context, diceDBCmd *cmd.DiceDBCm } cmdList = append(cmdList, watchCmd) isWatchNotification = true + + case Unwatch: + // Generate the Cmd being unwatched. All we need to do is remove the .UNWATCH suffix from the command and pass + // it along as is. + // Modify the command name to remove the .UNWATCH suffix, this will allow us to generate a consistent + // fingerprint (which uses the command name without the suffix) + diceDBCmd.Cmd = diceDBCmd.Cmd[:len(diceDBCmd.Cmd)-8] + watchCmd := &cmd.DiceDBCmd{ + Cmd: diceDBCmd.Cmd, + Args: diceDBCmd.Args, + } + cmdList = append(cmdList, watchCmd) + isWatchNotification = false + } + } + + // Unsubscribe Unwatch command type + if meta.CmdType == Unwatch { + // extract the fingerprint + command := cmdList[len(cmdList)-1] + fp, fperr := strconv.ParseUint(command.Args[0], 10, 32) + if fperr != nil { + err := w.ioHandler.Write(ctx, diceerrors.ErrInvalidFingerprint) + if err != nil { + return fmt.Errorf("error sending push response to client: %v", err) + } + return fperr + } + + // send the unsubscribe request + w.cmdWatchSubscriptionChan <- watchmanager.WatchSubscription{ + Subscribe: false, + AdhocReqChan: w.adhocReqChan, + Fingerprint: uint32(fp), + } + + err := w.ioHandler.Write(ctx, "OK") + if err != nil { + return fmt.Errorf("error sending push response to client: %v", err) } + return nil } // Scatter the broken-down commands to the appropriate shards. diff --git a/main.go b/main.go index e11a66479..20fbc109b 100644 --- a/main.go +++ b/main.go @@ -174,7 +174,7 @@ func main() { var ( queryWatchChan chan dstore.QueryWatchEvent cmdWatchChan chan dstore.CmdWatchEvent - cmdWatchSubscriptionChan chan watchmanager.WatchSubscription + cmdWatchSubscriptionChan = make(chan watchmanager.WatchSubscription) serverErrCh = make(chan error, 2) ) From 003c6c5f183367c92df026473362b8cd7e1d0848 Mon Sep 17 00:00:00 2001 From: Anchal Singh <77047523+anchalsingh25@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:45:22 +0530 Subject: [PATCH 13/20] #1021 #785 #819 Command migration for HSET, HMSET, HGET, HMGET, HSETNX, HDEL #1219 --- docs/src/content/docs/commands/HDEL.md | 132 ++++++ docs/src/content/docs/commands/HGET.md | 101 +++-- docs/src/content/docs/commands/HMGET.md | 108 +++++ docs/src/content/docs/commands/HMSET.md | 107 +++++ docs/src/content/docs/commands/HSET.md | 114 ++++-- docs/src/content/docs/commands/HSETNX.md | 105 +++++ .../commands/async/hgetall_test.go | 4 + .../commands/{async => resp}/hdel_test.go | 2 +- .../commands/{async => resp}/hget_test.go | 2 +- .../commands/{async => resp}/hmget_test.go | 2 +- .../commands/{async => resp}/hmset_test.go | 2 +- .../commands/{async => resp}/hset_test.go | 2 +- .../commands/{async => resp}/hsetnx_test.go | 2 +- internal/eval/commands.go | 50 ++- internal/eval/eval.go | 174 -------- internal/eval/eval_test.go | 378 ++++++++++++------ internal/eval/hmap.go | 38 +- internal/eval/hmap_test.go | 28 +- internal/eval/store_eval.go | 252 ++++++++++++ internal/server/cmd_meta.go | 31 +- internal/worker/cmd_meta.go | 24 ++ 21 files changed, 1223 insertions(+), 435 deletions(-) create mode 100644 docs/src/content/docs/commands/HDEL.md create mode 100644 docs/src/content/docs/commands/HMGET.md create mode 100644 docs/src/content/docs/commands/HMSET.md create mode 100644 docs/src/content/docs/commands/HSETNX.md rename integration_tests/commands/{async => resp}/hdel_test.go (98%) rename integration_tests/commands/{async => resp}/hget_test.go (98%) rename integration_tests/commands/{async => resp}/hmget_test.go (99%) rename integration_tests/commands/{async => resp}/hmset_test.go (99%) rename integration_tests/commands/{async => resp}/hset_test.go (98%) rename integration_tests/commands/{async => resp}/hsetnx_test.go (98%) diff --git a/docs/src/content/docs/commands/HDEL.md b/docs/src/content/docs/commands/HDEL.md new file mode 100644 index 000000000..64e2bfbc0 --- /dev/null +++ b/docs/src/content/docs/commands/HDEL.md @@ -0,0 +1,132 @@ +--- +title: HDEL +description: The HDEL command in DiceDB deletes a specified field within a hash stored at a given key. If either the key or field does not exist, no action is taken, and 0 is returned. +--- + +# HDEL +The HDEL command in DiceDB deletes a specified field within a hash stored at a given key. If either the key or field does not exist, no action is taken, and 0 is returned. + +## Syntax + +```bash +HDEL key field [field ...] +``` + +## Parameters + +| Parameter | Description | Type | Required | +|-----------|-------------------------------------------------------------------|---------|----------| +| `key` | The key of the hash from which the field(s) are to be deleted. | String | Yes | +| `field` | One or more fields within the hash to be deleted. | String | Yes | + +## Return Values + +| Condition | Return Value | +|---------------------------------------------|-----------------------------------------------------------------------------| +| Field(s) deleted successfully | `Integer` (Number of fields deleted) | +| Field does not exist | `0` | +| Key does not exist | `0` | +| Wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hdel' command` | + +## Behaviour + +When the `HDEL` command is executed, DiceDB performs the following steps: + +- It checks if the key exists in the database. +- If the key exists and is of type hash, it then checks for the specified field(s) within the hash. +- If the specified field(s) exist, they are deleted, and DiceDB returns the count of deleted fields. +- If the key does not exist or the field(s) are not present, it returns `0`. + +## Errors + +1. `Non-hash type or wrong data type`: + + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. + +2. `Incorrect Argument Count`: + + - Error Message: `(error) ERR wrong number of arguments for 'hdel' command` + - Occurs if the command is not provided with the correct number of arguments (i.e., fewer than two). + +## Example Usage + +### Basic Usage + +#### Deleting a field from a hash + +```bash +127.0.0.1:7379> HSET user:1000 name "John Doe" +(integer) 1 +127.0.0.1:7379> HDEL user:1000 name +(integer) 1 +127.0.0.1:7379> HGET user:1000 name +(nil) +``` + +#### Deleting multiple fields from a hash + +```bash +127.0.0.1:7379> HSET user:1000 name "John Doe" +(integer) 1 +127.0.0.1:7379> HSET user:1000 age "30" +(integer) 1 +127.0.0.1:7379> HDEL user:1000 name age +(integer) 2 +``` + +#### Field does not exist + +```bash +127.0.0.1:7379> HDEL user:1000 email +(integer) 0 +``` + +#### Key does not exist + +```bash +127.0.0.1:7379> HDEL user:2000 name +(integer) 0 +``` + +#### Key is not a hash + +```bash +127.0.0.1:7379> SET user:3000 "Not a hash" +OK +127.0.0.1:7379> HDEL user:3000 name +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +### Invalid Usage + +Attempting to delete a field in a key that is not a hash + +```bash +127.0.0.1:7379> SET user:5000 "This is a string" +OK +127.0.0.1:7379> HDEL user:5000 name +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +- **Behavior**: The `SET` command sets the key `user:5000` to a string value. +- **Error**: The `HDEL` command raises a `WRONGTYPE` error because user:5000 is not a hash, and `HDEL` only operates on hash data structures. + +Wrong Number of Arguments for HDEL Command + +```bash +127.0.0.1:7379> HDEL +(error) ERR wrong number of arguments for 'hdel' command + +127.0.0.1:7379> HDEL user:5000 +(error) ERR wrong number of arguments for 'hdel' command +``` +- **Behavior**: The `HDEL` command requires at least two arguments: the key and the field name. +- **Error**: The command fails because `HDEL` requires at least a `key` and one `field` as arguments. If these are not provided, DiceDB raises an error indicating an incorrect number of arguments. + +## Notes + +- The `HDEL` command is essential for managing hash data in DiceDB, allowing fields to be efficiently removed when no longer needed. + + diff --git a/docs/src/content/docs/commands/HGET.md b/docs/src/content/docs/commands/HGET.md index 47195a91d..41066ea10 100644 --- a/docs/src/content/docs/commands/HGET.md +++ b/docs/src/content/docs/commands/HGET.md @@ -1,6 +1,6 @@ --- title: HGET -description: Documentation for the DiceDB command HGET +description: The `HGET` command in DiceDB is used to retrieve the value associated with a specified field within a hash stored at a given key. If the key or the field does not exist, the command returns a `nil` value. --- The `HGET` command in DiceDB is used to retrieve the value associated with a specified field within a hash stored at a given key. If the key or the field does not exist, the command returns a `nil` value. @@ -13,84 +13,107 @@ HGET key field ## Parameters -- `key`: The key of the hash from which the field's value is to be retrieved. This is a string. -- `field`: The field within the hash whose value is to be retrieved. This is also a string. +| Parameter | Description | Type | Required | +|-----------|-----------------------------------------------------------------------|---------|----------| +| `key` | The key of the hash from which the field's value is to be retrieved. | String | Yes | +| `field` | The field within the hash whose value is to be retrieved. | String | Yes | -## Return Value +## Return Values -- `String`: The value associated with the specified field within the hash. -- `nil`: If the key does not exist or the field is not present in the hash. +| Condition | Return Value | +|---------------------------------------------|-----------------------------------------------------------------------------| +| Field exists in the hash | `String` (value of the field) | +| Key does not exist or field not present | `nil` | +| Wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hget' command` | ## Behaviour When the `HGET` command is executed, DiceDB performs the following steps: -1. It checks if the key exists in the database. -2. If the key exists and is of type hash, it then checks if the specified field exists within the hash. -3. If the field exists, it retrieves and returns the value associated with the field. -4. If the key does not exist or the field is not present in the hash, it returns `nil`. +- It checks if the key exists in the database. +- If the key exists and is of type hash, it then checks if the specified field exists within the hash. +- If the field exists, it retrieves and returns the value associated with the field. +- If the key does not exist or the field is not present in the hash, it returns `nil`. -## Error Handling +## Errors -- `WRONGTYPE Operation against a key holding the wrong kind of value`: This error is raised if the key exists but is not of type hash. DiceDB expects the key to be associated with a hash data structure for the `HGET` command to work correctly. +1. `Non-hash type or wrong data type`: -## Example Usage + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. -### Example 1: Retrieving a value from a hash +2. `Incorrect Argument Count`: -```bash -HSET user:1000 name "John Doe" -HSET user:1000 age "30" -HGET user:1000 name -``` + - Error Message: `(error) ERR wrong number of arguments for 'hget' command` + - Occurs if the command is not provided with the correct number of arguments (i.e., fewer than two). -`Output:` +## Example Usage -``` -"John Doe" -``` +### Basic Usage -### Example 2: Field does not exist +#### Retrieving a value from a hash ```bash -HGET user:1000 email +127.0.0.1:7379> HSET user:1000 name "John Doe" +(integer) 1 +127.0.0.1:7379> HSET user:1000 age "30" +(integer) 1 +127.0.0.1:7379> HGET user:1000 name +"John Doe" ``` -`Output:` +#### Field does not exist -``` +```bash +127.0.0.1:7379> HGET user:1000 email (nil) ``` -### Example 3: Key does not exist +#### Key does not exist ```bash -HGET user:2000 name +127.0.0.1:7379> HGET user:2000 name +(nil) ``` -`Output:` +### Key is not a hash +```bash +127.0.0.1:7379> SET user:3000 "Not a hash" +OK +127.0.0.1:7379> HGET user:3000 name +(error) WRONGTYPE Operation against a key holding the wrong kind of value ``` -(nil) -``` -### Example 4: Key is not a hash +### Invalid Usage + +Trying to get a field from a key that is not a hash. ```bash -SET user:3000 "Not a hash" -HGET user:3000 name +127.0.0.1:7379> SET product:2000 "This is a string" +OK +127.0.0.1:7379> HGET product:2000 name +(error) WRONGTYPE Operation against a key holding the wrong kind of value ``` +- **Behavior**: The `SET` command sets the key `product:2000` to a string value. +- **Error**: The `HGET` command will raise a WRONGTYPE error because `product:2000` is not a hash. -`Output:` +Wrong Number of Arguments for HGET Command +```bash +127.0.0.1:7379> HGET product:2000 +(error) ERR wrong number of arguments for 'hget' command + +127.0.0.1:7379> HGET product:2000 name name2 +(error) ERR wrong number of arguments for 'hget' command ``` -(error) WRONGTYPE Operation against a key holding the wrong kind of value -``` +- **Behavior**: The `HGET` command requires exactly two arguments: the key and the field name. +- **Error**: The command will raise an error if provided with an incorrect number of arguments, indicating that the command requires a specific count. ## Notes - The `HGET` command is a read-only command and does not modify the hash or any other data in the DiceDB database. -- It is a constant time operation, O(1), meaning it executes in the same amount of time regardless of the size of the hash. By understanding the `HGET` command, you can efficiently retrieve values from hashes stored in your DiceDB database, ensuring that your application can access the necessary data quickly and reliably. diff --git a/docs/src/content/docs/commands/HMGET.md b/docs/src/content/docs/commands/HMGET.md new file mode 100644 index 000000000..186daebde --- /dev/null +++ b/docs/src/content/docs/commands/HMGET.md @@ -0,0 +1,108 @@ +--- +title: HMGET +description: The `HMGET` command in DiceDB is used to retrieve the values of one or more specified fields from a hash. It allows efficient fetching of specific fields from a hash without retrieving the entire hash. +--- + +The `HMGET` command in DiceDB is used to retrieve the values of one or more specified fields from a hash. It allows efficient fetching of specific fields from a hash without retrieving the entire hash. + +## Syntax + +```bash +HMGET key field [field ...] +``` + +## Parameters + +| Parameter | Description | Type | Required | +|-----------------|---------------------------------------------------------------|---------|----------| +| `key` | The name of the hash. | String | Yes | +| `field` | The field within the hash to retrieve the value for. | String | Yes | +| `[field ...]` | Additional fields to retrieve from the hash. | String | No | + +## Return Values + +| Condition | Return Value | +|----------------------------------------------|-----------------------------------------------------------------------------| +| Field exists | `String` (The value of the field) | +| Field does not exist | `nil` | +| Multiple fields retrieved | List of values for each field | +| Wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hmget' command` | + +## Behaviour + +When the `HMGET` command is executed, the following actions occur: + +- The specified fields are fetched from the hash. +- If a field exists, its value is returned. +- If a field does not exist, `nil` is returned for that field. +- The command returns a list of values, corresponding to each field requested. + +## Errors + +The `HMGET` command can raise errors in the following scenarios: + +1. `Non-hash type or wrong data type`: + + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. + +2. `Incorrect Argument Count`: + + - Error Message: `(error) ERR wrong number of arguments for 'hmget' command` + - Occurs if the command is not provided with the correct number of arguments (i.e., fewer than two). + +## Example Usage + +### Basic Usage + +#### Retrieving Multiple Fields + +```bash +127.0.0.1:7379> HMGET product:2000 name price stock +1) "Laptop" +2) "999.99" +3) "50" +``` +- **Behaviour**: The values of the fields `name`, `price`, and `stock` from the hash `product:2000` are retrieved and returned in the order specified. +- **Return Value**: List of field values. + +#### Retrieving Fields with Missing Values + +```bash +127.0.0.1:7379> HMGET product:2000 name description +1) "Laptop" +2) (nil) +``` +- **Behaviour**: The `name` field exists, so its value is returned. The `description` field does not exist in the hash, so `nil` is returned. +- **Return Value**: List with value and `nil`. + +### Invalid Usage + +Trying to retrieve fields from a key that is not a hash. + +```bash +127.0.0.1:7379> SET product:2000 "This is a string" +OK +127.0.0.1:7379> HMGET product:2000 name price +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +- **Behaviour**: The `SET` command sets the key `product:2000` to a string value. +- **Error**: The `HMGET` command will raise a `WRONGTYPE` error because `product:2000` is not a hash. + +Missing Key or Field Arguments + +```bash +127.0.0.1:7379> HMGET +(error) ERR wrong number of arguments for 'hmget' command + +127.0.0.1:7379> HMGET product:2000 +(error) ERR wrong number of arguments for 'hmget' command +``` +- **Behavior**: The `HGET` command requires at least two arguments: the key and the field name. +- **Error**: The command fails if no key or fields are specified. DiceDB raises an error indicating that the number of arguments is incorrect. + +### Best Practices + +- Use `HMGET` to fetch only the fields you need from a hash to minimize data transfer and improve performance. diff --git a/docs/src/content/docs/commands/HMSET.md b/docs/src/content/docs/commands/HMSET.md new file mode 100644 index 000000000..4b07238e7 --- /dev/null +++ b/docs/src/content/docs/commands/HMSET.md @@ -0,0 +1,107 @@ +--- +title: HMSET +description: The `HMSET` command in DiceDB is used to set multiple field-value pairs in a hash at once. If the hash does not exist, a new hash is created. This command is efficient for setting multiple fields at once within a hash data structure. +--- + +The `HMSET` command in DiceDB is used to set multiple field-value pairs in a hash at once. If the hash does not exist, a new hash is created. This command is efficient for setting multiple fields at once within a hash data structure. + +## Syntax + +```bash +HMSET key field value [field value ...] +``` + +## Parameters + +| Parameter | Description | Type | Required | +|---------------------|---------------------------------------------------------------|---------|----------| +| `key` | The name of the hash. | String | Yes | +| `field` | The field within the hash to set the value for. | String | Yes | +| `value` | The value to set for the specified field. | String | Yes | +| `[field value ...]` | Additional field-value pairs to set in the hash. | String | No | + +## Return Values + +| Condition | Return Value | +|----------------------------------------------|-----------------------------------------------------------------------------| +| A new field added | `OK` | +| Existing field updated | `OK` | +| Multiple fields added | `OK` | +| Non-hash type or wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hmset' command` | + + +## Behaviour + +When the `HMSET` command is executed, the following actions occur: + +- If the specified hash does not exist, a new hash is created. +- The specified fields and values are set in the hash. +- If any field already exists, its value is updated with the new value provided. +- The command returns `OK` to indicate successful execution. + +## Errors + +The `HMSET` command can raise errors in the following scenarios: + +1. `Non-hash type or wrong data type`: + + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. + +2. `Incorrect Argument Count`: + + - Error Message: `(error) ERR wrong number of arguments for 'hmset' command` + - Occurs if the command is not provided with the correct number of arguments (i.e., an even number of arguments after the key). + +## Example Usage + +### Basic Usage + +#### Creating a New Hash with Multiple Fields + +```bash +127.0.0.1:7379> HMSET product:4000 name "Tablet" price 299.99 stock 30 +OK +``` +- **Behaviour**: A new hash is created with the key `product:4000`. The fields `name`, `price`, and `stock` are set with the respective values. +- **Return Value**: `OK` + +#### Updating an Existing Hash with Multiple Fields + +```bash +127.0.0.1:7379> HMSET product:4000 price 279.99 stock 25 +OK +``` +- **Behaviour**: The `price` and `stock` fields in the hash `product:4000` are updated with the new values. +- **Return Value**: `OK` + +### Invalid Usage + +Trying to set fields in a key that is not a hash. + +```bash +127.0.0.1:7379> SET product:4000 "This is a string" +OK +127.0.0.1:7379> HMSET product:4000 name "Tablet" +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +- **Behaviour**: The `SET` command sets the key `product:4000` to a string value. +- **Error**: The `HMSET` command will raise a `WRONGTYPE` error because `product:4000` is not a hash. + +Wrong Number of Arguments for HMSET Command + +```bash +127.0.0.1:7379> HMSET product:4000 +(error) ERR wrong number of arguments for 'hmset' command + +127.0.0.1:7379> HMSET product:4000 name +(error) ERR wrong number of arguments for 'hmset' command +``` +- **Behavior**: The `HMSET` command requires atleast three arguments: the key, the field name, and the field value. +- **Error**: The command fails because it requires at least one field-value pair in addition to the key. If insufficient arguments are provided, DiceDB raises an error indicating that the number of arguments is incorrect. + +### Best Practices + +- **Use HMSET for Batch Updates**: Utilize `HMSET` when you need to set multiple fields at once in a hash to reduce command overhead and improve performance. diff --git a/docs/src/content/docs/commands/HSET.md b/docs/src/content/docs/commands/HSET.md index 32ed2ed39..d3eeed0a0 100644 --- a/docs/src/content/docs/commands/HSET.md +++ b/docs/src/content/docs/commands/HSET.md @@ -1,6 +1,6 @@ --- title: HSET -description: Documentation for the DiceDB command HSET +description: The `HSET` command in DiceDB is used to set the value of a field in a hash. If the hash does not exist, a new hash is created. If the field already exists in the hash, the value is updated. This command is useful for managing and storing key-value pairs within a hash data structure. --- The `HSET` command in DiceDB is used to set the value of a field in a hash. If the hash does not exist, a new hash is created. If the field already exists in the hash, the value is updated. This command is useful for managing and storing key-value pairs within a hash data structure. @@ -13,83 +13,123 @@ HSET key field value [field value ...] ## Parameters -- `key`: The name of the hash. -- `field`: The field within the hash to set the value for. -- `value`: The value to set for the specified field. -- `[field value ...]`: Optional additional field-value pairs to set in the hash. +| Parameter | Description | Type | Required | +|---------------------|---------------------------------------------------------------|---------|----------| +| `key` | The name of the hash. | String | Yes | +| `field` | The field within the hash to set the value for. | String | Yes | +| `value` | The value to set for the specified field. | String | Yes | +| `[field value ...]` | Optional additional field-value pairs to set in the hash. | String | No | -## Return Value +## Return Values -- `Integer`: The number of fields that were added to the hash, not including fields that were already present and updated. +| Condition | Return Value | +|---------------------------------------------|-----------------------------------------------------------------------------| +| A new field added | `1` | +| Existing field updated | `0` | +| Multiple fields added | `Integer` (count of new fields) | +| Wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hset' command` | ## Behaviour When the `HSET` command is executed, the following actions occur: -1. If the specified hash does not exist, a new hash is created. -2. The specified field(s) and value(s) are set in the hash. -3. If a field already exists, its value is updated with the new value provided. -4. The command returns the number of fields that were newly added to the hash. +- If the specified hash does not exist, a new hash is created. +- The specified field(s) and value(s) are set in the hash. +- If a field already exists, its value is updated with the new value provided. +- The command returns the number of fields that were newly added to the hash. -## Error Handling +## Errors The `HSET` command can raise errors in the following scenarios: -1. `WRONGTYPE Operation against a key holding the wrong kind of value`: This error occurs if the key exists but is not a hash. -2. `ERR wrong number of arguments for 'hset' command`: This error occurs if the command is not provided with the correct number of arguments (i.e., an even number of arguments after the key). +1. `Non-hash type or wrong data type`: + + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. + +2. `Incorrect Argument Count`: + + - Error Message: `(error) ERR wrong number of arguments for 'hset' command` + - occurs if the command is not provided with the correct number of arguments (i.e., an even number of arguments after the key). ## Example Usage -### Basic Example +### Basic Usage + +#### Creating a New Hash ```bash -HSET user:1000 name "John Doe" age 30 +127.0.0.1:7379> HSET product:2000 name "Laptop" price 999.99 stock 50 +3 ``` +- **Behaviour**: A new hash is created with the key `product:2000`. The fields `name`, `price`, and `stock` are set with the respective values. +- **Return Value**: `3` (since three new fields were added). -This command sets the `name` field to "John Doe" and the `age` field to 30 in the hash stored at `user:1000`. If the hash does not exist, it will be created. +#### Updating an Existing Hash -### Multiple Field-Value Pairs +Updating existing fields in a hash `product:2000`. ```bash -HSET user:1000 name "John Doe" age 30 email "john.doe@example.com" +127.0.0.1:7379> HSET product:2000 price 899.99 stock 45 ``` -This command sets the `name`, `age`, and `email` fields in the hash stored at `user:1000`. If the hash does not exist, it will be created. +- **Behavior**: The `price` and `stock` fields in the hash `product:2000` are updated with the new values. +- **Return Value**: `0` (since no new fields were added, only existing fields were updated). + +#### Setting Multiple Field-Value Pairs -### Updating Existing Fields +Setting multiple fields in a hash `user:1000`. ```bash -HSET user:1000 age 31 +127.0.0.1:7379> HSET user:1000 name "John Doe" age 30 email "john.doe@example.com" ``` -This command updates the `age` field to 31 in the hash stored at `user:1000`. If the `age` field already exists, its value is updated. +- **Behavior**: This command sets the `name`, `age`, and `email` fields in the hash stored at `user:1000`. If the hash does not exist, it will be created. +- **Return Value**: `3` (if all three fields were added). -## Detailed Example +#### Updating Existing Fields -### Creating a New Hash +Updating a field in an existing hash `user:1000`. ```bash -HSET product:2000 name "Laptop" price 999.99 stock 50 +127.0.0.1:7379> HSET user:1000 age 31 ``` -- `Behaviour`: A new hash is created with the key `product:2000`. The fields `name`, `price`, and `stock` are set with the respective values. -- `Return Value`: `3` (since three new fields were added). +- **Behavior**: This command updates the `age` field to 31 in the hash stored at `user:1000`. If the `age` field already exists, its value is updated. +- **Return Value**: `0` (if the field was already present and only updated). -### Updating an Existing Hash +### Invalid Usage + +Trying to set a field in a key that is not a hash. ```bash -HSET product:2000 price 899.99 stock 45 +127.0.0.1:7379> SET product:2000 "This is a string" +OK +127.0.0.1:7379> HSET product:2000 name "Laptop" +(error) WRONGTYPE Operation against a key holding the wrong kind of value ``` -- `Behaviour`: The `price` and `stock` fields in the hash `product:2000` are updated with the new values. -- `Return Value`: `0` (since no new fields were added, only existing fields were updated). +- **Behavior**: The `SET` command sets the key `product:2000` to a string value. +- **Error**: The `HSET` command will raise a `WRONGTYPE` error because `product:2000` is not a hash. -### Error Handling Example +Wrong Number of Arguments for HSET Command ```bash -SET product:2000 "This is a string" -HSET product:2000 name "Laptop" +127.0.0.1:7379> HSET product:2000 +(error) ERR wrong number of arguments for 'hset' command + +127.0.0.1:7379> HSET product:2000 name +(error) ERR wrong number of arguments for 'hset' command ``` +- **Behavior**: The `HSET` command requires atleast three arguments: the key, the field name, and the field value. +- **Error**: The command fails because it requires at least one field-value pair in addition to the key. If insufficient arguments are provided, DiceDB raises an error indicating that the number of arguments is incorrect. + +### Best Practices + +- **Check for Existence**: Before updating fields, consider checking if the hash exists to avoid unnecessary updates. + +## Notes -- `Behaviour`: The `SET` command sets the key `product:2000` to a string value. -- `Error`: The `HSET` command will raise a `WRONGTYPE` error because `product:2000` is not a hash. +- `HSET` can also accept multiple field-value pairs, making it efficient for adding or updating multiple fields in a single command. +By understanding the `HSET` command, you can effectively update or expand hashes in your DiceDB database, allowing for quick modifications and optimizations when handling key-value pairs within hashes. diff --git a/docs/src/content/docs/commands/HSETNX.md b/docs/src/content/docs/commands/HSETNX.md new file mode 100644 index 000000000..994e4c2a8 --- /dev/null +++ b/docs/src/content/docs/commands/HSETNX.md @@ -0,0 +1,105 @@ +--- +title: HSETNX +description: The `HSETNX` command in DiceDB is used to set the value of a field in a hash only if the field does not already exist. This command is useful for ensuring that a value is only set if it is not already present. +--- + +The `HSETNX` command in DiceDB is used to set the value of a field in a hash only if the field does not already exist. This command is useful for ensuring that a value is only set if it is not already present. + +## Syntax + +```bash +HSETNX key field value +``` + +## Parameters + +| Parameter | Description | Type | Required | +|-----------|----------------------------------------------------------|---------|----------| +| `key` | The name of the hash. | String | Yes | +| `field` | The field within the hash to set the value for. | String | Yes | +| `value` | The value to set for the specified field. | String | Yes | + +## Return Values + +| Condition | Return Value | +|---------------------------------------------|-----------------------------------------------------------------------------| +| Field added | `1` | +| Field already exists | `0` | +| Wrong data type | `(error) WRONGTYPE Operation against a key holding the wrong kind of value` | +| Incorrect Argument Count | `(error) ERR wrong number of arguments for 'hsetnx' command` | + +## Behaviour + +When the `HSETNX` command is executed, the following actions occur: + +- If the specified hash does not exist, a new hash is created. +- The specified field and value are set in the hash only if the field does not already exist. +- If the field already exists, the command does not modify the existing value and returns `0`. +- The command returns `1` if the field was successfully added to the hash. + +## Errors + +The `HSETNX` command can raise errors in the following scenarios: + +1. `Non-hash type or wrong data type`: + + - Error Message: `(error) WRONGTYPE Operation against a key holding the wrong kind of value` + - Occurs if `key` holds a non-hash data structure. + +2. `Incorrect Argument Count`: + + - Error Message: `(error) ERR wrong number of arguments for 'hsetnx' command` + - Occurs if the command is not provided with the correct number of arguments (i.e., fewer than three). + +## Example Usage + +### Basic Usage + +#### Creating a New Hash with `HSETNX` + +```bash +127.0.0.1:7379> HSETNX product:3000 name "Smartphone" +1 +``` +- **Behaviour**: A new hash is created with the key `product:3000`. The field `name` is set with the value "Smartphone". +- **Return Value**: `1` (since the field was added). + +#### Attempting to Set an Existing Field + +```bash +127.0.0.1:7379> HSETNX product:3000 name "Tablet" +0 +``` + +- **Behaviour**: The command attempts to set the `name` field to "Tablet", but since `name` already exists in the hash, it does not change the value. +- **Return Value**: `0` (since the field was not added). + +### Invalid Usage + +Trying to set a field in a key that is not a hash. + +```bash +127.0.0.1:7379> SET product:3000 "This is a string" +OK +127.0.0.1:7379> HSETNX product:3000 name "Smartphone" +(error) WRONGTYPE Operation against a key holding the wrong kind of value +``` + +- **Behaviour**: The `SET` command sets the key `product:3000` to a string value. +- **Error**: The `HSETNX` command raises a `WRONGTYPE` error because `product:3000` is not a hash. + +Wrong Number of Arguments for `HSETNX` Command + +```bash +127.0.0.1:7379> HSETNX product:3000 +(error) ERR wrong number of arguments for 'hsetnx' command + +127.0.0.1:7379> HSETNX product:3000 name +(error) ERR wrong number of arguments for 'hsetnx' command +``` +- **Behavior**: The `HSETNX` command requires atleast three arguments: the key, the field name, and the field value. +- **Error**: The command fails because it requires the `key`, `field`, and `value` parameters. If insufficient arguments are provided, DiceDB raises an error indicating that the number of arguments is incorrect. + +### Best Practices + +- Use `HSETNX` when you need to ensure that a field is only set if it does not already exist, preventing accidental overwrites. diff --git a/integration_tests/commands/async/hgetall_test.go b/integration_tests/commands/async/hgetall_test.go index 72448f64b..7971f0cc4 100644 --- a/integration_tests/commands/async/hgetall_test.go +++ b/integration_tests/commands/async/hgetall_test.go @@ -7,6 +7,10 @@ import ( "github.com/stretchr/testify/assert" ) +var ZERO int64 = 0 +var ONE int64 = 1 +var TWO int64 = 2 + func TestHGETALL(t *testing.T) { conn := getLocalConnection() defer conn.Close() diff --git a/integration_tests/commands/async/hdel_test.go b/integration_tests/commands/resp/hdel_test.go similarity index 98% rename from integration_tests/commands/async/hdel_test.go rename to integration_tests/commands/resp/hdel_test.go index eb6bff455..f931f781a 100644 --- a/integration_tests/commands/async/hdel_test.go +++ b/integration_tests/commands/resp/hdel_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/integration_tests/commands/async/hget_test.go b/integration_tests/commands/resp/hget_test.go similarity index 98% rename from integration_tests/commands/async/hget_test.go rename to integration_tests/commands/resp/hget_test.go index 3b0618c91..79489550b 100644 --- a/integration_tests/commands/async/hget_test.go +++ b/integration_tests/commands/resp/hget_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/integration_tests/commands/async/hmget_test.go b/integration_tests/commands/resp/hmget_test.go similarity index 99% rename from integration_tests/commands/async/hmget_test.go rename to integration_tests/commands/resp/hmget_test.go index cad7e7894..ad946e9cf 100644 --- a/integration_tests/commands/async/hmget_test.go +++ b/integration_tests/commands/resp/hmget_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/integration_tests/commands/async/hmset_test.go b/integration_tests/commands/resp/hmset_test.go similarity index 99% rename from integration_tests/commands/async/hmset_test.go rename to integration_tests/commands/resp/hmset_test.go index ce2508436..44e68d0a6 100644 --- a/integration_tests/commands/async/hmset_test.go +++ b/integration_tests/commands/resp/hmset_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "github.com/stretchr/testify/assert" diff --git a/integration_tests/commands/async/hset_test.go b/integration_tests/commands/resp/hset_test.go similarity index 98% rename from integration_tests/commands/async/hset_test.go rename to integration_tests/commands/resp/hset_test.go index 81240c6d0..272df8889 100644 --- a/integration_tests/commands/async/hset_test.go +++ b/integration_tests/commands/resp/hset_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/integration_tests/commands/async/hsetnx_test.go b/integration_tests/commands/resp/hsetnx_test.go similarity index 98% rename from integration_tests/commands/async/hsetnx_test.go rename to integration_tests/commands/resp/hsetnx_test.go index dfb4b6e9d..8e59e3160 100644 --- a/integration_tests/commands/async/hsetnx_test.go +++ b/integration_tests/commands/resp/hsetnx_test.go @@ -1,4 +1,4 @@ -package async +package resp import ( "testing" diff --git a/internal/eval/commands.go b/internal/eval/commands.go index c046926af..ba877edc2 100644 --- a/internal/eval/commands.go +++ b/internal/eval/commands.go @@ -705,9 +705,10 @@ var ( Returns This command returns the number of keys that are stored at given key. `, - Eval: evalHSET, - Arity: -4, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalHSET, + Arity: -4, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hmsetCmdMeta = DiceCmdMeta{ Name: "HMSET", @@ -717,9 +718,10 @@ var ( Returns This command returns the number of keys that are stored at given key. `, - Eval: evalHMSET, - Arity: -4, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalHMSET, + Arity: -4, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hkeysCmdMeta = DiceCmdMeta{ Name: "HKEYS", @@ -734,23 +736,26 @@ var ( Info: `Sets field in the hash stored at key to value, only if field does not yet exist. If key does not exist, a new key holding a hash is created. If field already exists, this operation has no effect.`, - Eval: evalHSETNX, - Arity: 4, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalHSETNX, + Arity: 4, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hgetCmdMeta = DiceCmdMeta{ - Name: "HGET", - Info: `Returns the value associated with field in the hash stored at key.`, - Eval: evalHGET, - Arity: -3, - KeySpecs: KeySpecs{BeginIndex: 1}, + Name: "HGET", + Info: `Returns the value associated with field in the hash stored at key.`, + NewEval: evalHGET, + Arity: -3, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hmgetCmdMeta = DiceCmdMeta{ - Name: "HMGET", - Info: `Returns the values associated with the specified fields in the hash stored at key.`, - Eval: evalHMGET, - Arity: -2, - KeySpecs: KeySpecs{BeginIndex: 1}, + Name: "HMGET", + Info: `Returns the values associated with the specified fields in the hash stored at key.`, + NewEval: evalHMGET, + Arity: -2, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hgetAllCmdMeta = DiceCmdMeta{ Name: "HGETALL", @@ -795,9 +800,10 @@ var ( If key does not exist, it is treated as an empty hash and this command returns 0. Returns The number of fields that were removed from the hash, not including specified but non-existing fields.`, - Eval: evalHDEL, - Arity: -3, - KeySpecs: KeySpecs{BeginIndex: 1}, + NewEval: evalHDEL, + Arity: -3, + KeySpecs: KeySpecs{BeginIndex: 1}, + IsMigrated: true, } hscanCmdMeta = DiceCmdMeta{ Name: "HSCAN", diff --git a/internal/eval/eval.go b/internal/eval/eval.go index 459e65434..2bf259f6c 100644 --- a/internal/eval/eval.go +++ b/internal/eval/eval.go @@ -1928,98 +1928,6 @@ func evalCOPY(args []string, store *dstore.Store) []byte { return clientio.RespOne } -// evalHSET sets the specified fields to their -// respective values in a hashmap stored at key -// -// This command overwrites the values of specified -// fields that exist in the hash. -// -// If key doesn't exist, a new key holding a hash is created. -// -// Usage: HSET key field value [field value ...] -func evalHSET(args []string, store *dstore.Store) []byte { - if len(args) < 3 { - return diceerrors.NewErrArity("HSET") - } - - numKeys, err := insertInHashMap(args, store) - if err != nil { - return err - } - - return clientio.Encode(numKeys, false) -} - -// evalHMSET sets the specified fields to their -// respective values in a hashmap stored at key -// -// This command overwrites the values of specified -// fields that exist in the hash. -// -// If key doesn't exist, a new key holding a hash is created. -// -// Usage: HMSET key field value [field value ...] -func evalHMSET(args []string, store *dstore.Store) []byte { - if len(args) < 3 { - return diceerrors.NewErrArity("HMSET") - } - - _, err := insertInHashMap(args, store) - if err != nil { - return err - } - - return clientio.RespOK -} - -// helper function to insert key value in hashmap associated with the given hash -func insertInHashMap(args []string, store *dstore.Store) (numKeys int64, err2 []byte) { - key := args[0] - - obj := store.Get(key) - - var hashMap HashMap - - if obj != nil { - if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { - return 0, diceerrors.NewErrWithMessage(diceerrors.WrongTypeErr) - } - hashMap = obj.Value.(HashMap) - } - - keyValuePairs := args[1:] - hashMap, numKeys, err := hashMapBuilder(keyValuePairs, hashMap) - if err != nil { - return 0, diceerrors.NewErrWithMessage(err.Error()) - } - - obj = store.NewObj(hashMap, -1, object.ObjTypeHashMap, object.ObjEncodingHashMap) - - store.Put(key, obj) - - return numKeys, nil -} - -func evalHSETNX(args []string, store *dstore.Store) []byte { - if len(args) != 3 { - return diceerrors.NewErrArity("HSETNX") - } - - key := args[0] - hmKey := args[1] - - val, errWithMessage := getValueFromHashMap(key, hmKey, store) - if errWithMessage != nil { - return errWithMessage - } - if !bytes.Equal(val, clientio.RespNIL) { // hmKey is already present in hash map - return clientio.RespZero - } - - evalHSET(args, store) - return clientio.RespOne -} - func evalHGETALL(args []string, store *dstore.Store) []byte { if len(args) != 1 { return diceerrors.NewErrArity("HGETALL") @@ -2046,88 +1954,6 @@ func evalHGETALL(args []string, store *dstore.Store) []byte { return clientio.Encode(results, false) } -func evalHGET(args []string, store *dstore.Store) []byte { - if len(args) != 2 { - return diceerrors.NewErrArity("HGET") - } - - key := args[0] - hmKey := args[1] - - val, errWithMessage := getValueFromHashMap(key, hmKey, store) - if errWithMessage != nil { - return errWithMessage - } - return val -} - -// evalHMGET returns an array of values associated with the given fields, -// in the same order as they are requested. -// If a field does not exist, returns a corresponding nil value in the array. -// If the key does not exist, returns an array of nil values. -func evalHMGET(args []string, store *dstore.Store) []byte { - if len(args) < 2 { - return diceerrors.NewErrArity("HMGET") - } - key := args[0] - - obj := store.Get(key) - - results := make([]interface{}, len(args[1:])) - if obj == nil { - return clientio.Encode(results, false) - } - if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { - return diceerrors.NewErrWithMessage(diceerrors.WrongTypeErr) - } - - hashMap := obj.Value.(HashMap) - - for i, hmKey := range args[1:] { - hmValue, ok := hashMap.Get(hmKey) - if ok { - results[i] = *hmValue - } else { - results[i] = clientio.RespNIL - } - } - - return clientio.Encode(results, false) -} - -func evalHDEL(args []string, store *dstore.Store) []byte { - if len(args) < 2 { - return diceerrors.NewErrArity("HDEL") - } - - key := args[0] - fields := args[1:] - - obj := store.Get(key) - if obj == nil { - return clientio.Encode(0, false) - } - - if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { - return diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) - } - - hashMap := obj.Value.(HashMap) - count := 0 - for _, field := range fields { - if _, ok := hashMap[field]; ok { - delete(hashMap, field) - count++ - } - } - - if count > 0 { - store.Put(key, obj) - } - - return clientio.Encode(count, false) -} - func evalObjectIdleTime(key string, store *dstore.Store) []byte { obj := store.GetNoTouch(key) if obj == nil { diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 82c0346e8..29d7f4974 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -3046,21 +3046,30 @@ func testEvalPFMERGE(t *testing.T, store *dstore.Store) { func testEvalHGET(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "wrong number of args passed": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'hget' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HGET"), + }, }, "only key passed": { - setup: func() {}, - input: []string{"KEY"}, - output: []byte("-ERR wrong number of arguments for 'hget' command\r\n"), + setup: func() {}, + input: []string{"KEY"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HGET"), + }, }, - "key doesn't exists": { - setup: func() {}, - input: []string{"KEY", "field_name"}, - output: clientio.RespNIL, + "key doesn't exist": { + setup: func() {}, + input: []string{"KEY", "field_name"}, + migratedOutput: EvalResponse{ + Result: clientio.NIL, + Error: nil, + }, }, - "key exists but field_name doesn't exists": { + "key exists but field_name doesn't exist": { setup: func() { key := "KEY_MOCK" field := "mock_field_name" @@ -3075,10 +3084,13 @@ func testEvalHGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "non_existent_key"}, - output: clientio.RespNIL, + input: []string{"KEY_MOCK", "non_existent_key"}, + migratedOutput: EvalResponse{ + Result: clientio.NIL, + Error: nil, + }, }, - "both key and field_name exists": { + "both key and field_name exist": { setup: func() { key := "KEY_MOCK" field := "mock_field_name" @@ -3093,32 +3105,44 @@ func testEvalHGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "mock_field_name"}, - output: clientio.Encode("mock_field_value", false), + input: []string{"KEY_MOCK", "mock_field_name"}, + migratedOutput: EvalResponse{ + Result: "mock_field_value", + Error: nil, + }, }, } - runEvalTests(t, tests, evalHGET, store) + runMigratedEvalTests(t, tests, evalHGET, store) } func testEvalHMGET(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "wrong number of args passed": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'hmget' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMGET"), + }, }, "only key passed": { - setup: func() {}, - input: []string{"KEY"}, - output: []byte("-ERR wrong number of arguments for 'hmget' command\r\n"), + setup: func() {}, + input: []string{"KEY"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMGET"), + }, }, - "key doesn't exists": { - setup: func() {}, - input: []string{"KEY", "field_name"}, - output: clientio.Encode([]interface{}{nil}, false), + "key doesn't exist": { + setup: func() {}, + input: []string{"KEY", "field_name"}, + migratedOutput: EvalResponse{ + Result: []interface{}{nil}, + Error: nil, + }, }, - "key exists but field_name doesn't exists": { + "key exists but field_name doesn't exist": { setup: func() { key := "KEY_MOCK" field := "mock_field_name" @@ -3133,10 +3157,13 @@ func testEvalHMGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "non_existent_key"}, - output: clientio.Encode([]interface{}{nil}, false), + input: []string{"KEY_MOCK", "non_existent_key"}, + migratedOutput: EvalResponse{ + Result: []interface{}{nil}, + Error: nil, + }, }, - "both key and field_name exists": { + "both key and field_name exist": { setup: func() { key := "KEY_MOCK" field := "mock_field_name" @@ -3151,8 +3178,11 @@ func testEvalHMGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "mock_field_name"}, - output: clientio.Encode([]interface{}{"mock_field_value"}, false), + input: []string{"KEY_MOCK", "mock_field_name"}, + migratedOutput: EvalResponse{ + Result: []interface{}{"mock_field_value"}, + Error: nil, + }, }, "some fields exist some do not": { setup: func() { @@ -3169,12 +3199,15 @@ func testEvalHMGET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "field1", "field2", "field3", "field4"}, - output: clientio.Encode([]interface{}{"value1", "value2", nil, nil}, false), + input: []string{"KEY_MOCK", "field1", "field2", "field3", "field4"}, + migratedOutput: EvalResponse{ + Result: []interface{}{"value1", "value2", nil, nil}, // Use nil for non-existent fields + Error: nil, + }, }, } - runEvalTests(t, tests, evalHMGET, store) + runMigratedEvalTests(t, tests, evalHMGET, store) } func testEvalHVALS(t *testing.T, store *dstore.Store) { @@ -3398,37 +3431,51 @@ func testEvalHEXISTS(t *testing.T, store *dstore.Store) { func testEvalHDEL(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "HDEL with wrong number of args": { - input: []string{"key"}, - output: []byte("-ERR wrong number of arguments for 'hdel' command\r\n"), + input: []string{"key"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HDEL"), + }, }, "HDEL with key does not exist": { - input: []string{"nonexistent", "field"}, - output: clientio.RespZero, + input: []string{"nonexistent", "field"}, + migratedOutput: EvalResponse{ + Result: int64(0), + Error: nil, + }, }, "HDEL with key exists but not a hash": { setup: func() { evalSET([]string{"string_key", "string_value"}, store) }, - input: []string{"string_key", "field"}, - output: []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"), + input: []string{"string_key", "field"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + }, }, "HDEL with delete existing fields": { setup: func() { evalHSET([]string{"hash_key", "field1", "value1", "field2", "value2"}, store) }, - input: []string{"hash_key", "field1", "field2", "nonexistent"}, - output: clientio.Encode(int64(2), false), + input: []string{"hash_key", "field1", "field2", "nonexistent"}, + migratedOutput: EvalResponse{ + Result: int64(2), + Error: nil, + }, }, "HDEL with delete non-existing fields": { setup: func() { evalHSET([]string{"hash_key", "field1", "value1"}, store) }, - input: []string{"hash_key", "nonexistent1", "nonexistent2"}, - output: clientio.RespZero, + input: []string{"hash_key", "nonexistent1", "nonexistent2"}, + migratedOutput: EvalResponse{ + Result: int64(0), + Error: nil, + }, }, } - - runEvalTests(t, tests, evalHDEL, store) + runMigratedEvalTests(t, tests, evalHDEL, store) } func testEvalHSCAN(t *testing.T, store *dstore.Store) { @@ -3922,34 +3969,52 @@ func BenchmarkEvalHSET(b *testing.B) { func testEvalHSET(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "wrong number of args passed": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'hset' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSET"), + }, }, "only key passed": { - setup: func() {}, - input: []string{"key"}, - output: []byte("-ERR wrong number of arguments for 'hset' command\r\n"), + setup: func() {}, + input: []string{"key"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSET"), + }, }, "only key and field_name passed": { - setup: func() {}, - input: []string{"KEY", "field_name"}, - output: []byte("-ERR wrong number of arguments for 'hset' command\r\n"), + setup: func() {}, + input: []string{"KEY", "field_name"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSET"), + }, }, "key, field and value passed": { - setup: func() {}, - input: []string{"KEY1", "field_name", "value"}, - output: clientio.Encode(int64(1), false), + setup: func() {}, + input: []string{"KEY1", "field_name", "value"}, + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, "key, field and value updated": { - setup: func() {}, - input: []string{"KEY1", "field_name", "value_new"}, - output: clientio.Encode(int64(1), false), + setup: func() {}, + input: []string{"KEY1", "field_name", "value_new"}, + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, "new set of key, field and value added": { - setup: func() {}, - input: []string{"KEY2", "field_name_new", "value_new_new"}, - output: clientio.Encode(int64(1), false), + setup: func() {}, + input: []string{"KEY2", "field_name_new", "value_new_new"}, + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, "apply with duplicate key, field and value names": { setup: func() { @@ -3966,8 +4031,11 @@ func testEvalHSET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value"}, - output: clientio.Encode(int64(0), false), + input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value"}, + migratedOutput: EvalResponse{ + Result: int64(0), + Error: nil, + }, }, "same key -> update value, add new field and value": { setup: func() { @@ -3984,12 +4052,6 @@ func testEvalHSET(t *testing.T, store *dstore.Store) { } store.Put(key, obj) - - // Check if the map is saved correctly in the store - res, err := getValueFromHashMap(key, field, store) - - assert.Nil(t, err) - assert.Equal(t, res, clientio.Encode(mockValue, false)) }, input: []string{ "KEY_MOCK", @@ -3998,44 +4060,64 @@ func testEvalHSET(t *testing.T, store *dstore.Store) { "mock_field_name_new", "mock_value_new", }, - output: clientio.Encode(int64(1), false), + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, } - - runEvalTests(t, tests, evalHSET, store) + runMigratedEvalTests(t, tests, evalHSET, store) } func testEvalHMSET(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "wrong number of args passed": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'hmset' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMSET"), + }, }, "only key passed": { - setup: func() {}, - input: []string{"key"}, - output: []byte("-ERR wrong number of arguments for 'hmset' command\r\n"), + setup: func() {}, + input: []string{"key"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMSET"), + }, }, "only key and field_name passed": { - setup: func() {}, - input: []string{"KEY", "field_name"}, - output: []byte("-ERR wrong number of arguments for 'hmset' command\r\n"), + setup: func() {}, + input: []string{"KEY", "field_name"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMSET"), + }, }, "key, field and value passed": { - setup: func() {}, - input: []string{"KEY1", "field_name", "value"}, - output: clientio.RespOK, + setup: func() {}, + input: []string{"KEY1", "field_name", "value"}, + migratedOutput: EvalResponse{ + Result: clientio.OK, + Error: nil, + }, }, "key, field and value updated": { - setup: func() {}, - input: []string{"KEY1", "field_name", "value_new"}, - output: clientio.RespOK, + setup: func() {}, + input: []string{"KEY1", "field_name", "value_new"}, + migratedOutput: EvalResponse{ + Result: clientio.OK, + Error: nil, + }, }, "new set of key, field and value added": { - setup: func() {}, - input: []string{"KEY2", "field_name_new", "value_new_new"}, - output: clientio.RespOK, + setup: func() {}, + input: []string{"KEY2", "field_name_new", "value_new_new"}, + migratedOutput: EvalResponse{ + Result: clientio.OK, + Error: nil, + }, }, "apply with duplicate key, field and value names": { setup: func() { @@ -4052,8 +4134,11 @@ func testEvalHMSET(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value"}, - output: clientio.RespOK, + input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value"}, + migratedOutput: EvalResponse{ + Result: clientio.OK, + Error: nil, + }, }, "same key -> update value, add new field and value": { setup: func() { @@ -4070,12 +4155,6 @@ func testEvalHMSET(t *testing.T, store *dstore.Store) { } store.Put(key, obj) - - // Check if the map is saved correctly in the store - res, err := getValueFromHashMap(key, field, store) - - assert.True(t, err == nil) - assert.Equal(t, res, clientio.Encode(mockValue, false)) }, input: []string{ "KEY_MOCK", @@ -4084,11 +4163,13 @@ func testEvalHMSET(t *testing.T, store *dstore.Store) { "mock_field_name_new", "mock_value_new", }, - output: clientio.RespOK, + migratedOutput: EvalResponse{ + Result: clientio.OK, + Error: nil, + }, }, } - - runEvalTests(t, tests, evalHMSET, store) + runMigratedEvalTests(t, tests, evalHMSET, store) } func testEvalHKEYS(t *testing.T, store *dstore.Store) { @@ -5455,34 +5536,52 @@ func BenchmarkEvalHSETNX(b *testing.B) { func testEvalHSETNX(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "no args passed": { - setup: func() {}, - input: nil, - output: []byte("-ERR wrong number of arguments for 'hsetnx' command\r\n"), + setup: func() {}, + input: nil, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSETNX"), + }, }, "only key passed": { - setup: func() {}, - input: []string{"key"}, - output: []byte("-ERR wrong number of arguments for 'hsetnx' command\r\n"), + setup: func() {}, + input: []string{"key"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSETNX"), + }, }, "only key and field_name passed": { - setup: func() {}, - input: []string{"KEY", "field_name"}, - output: []byte("-ERR wrong number of arguments for 'hsetnx' command\r\n"), + setup: func() {}, + input: []string{"KEY", "field_name"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSETNX"), + }, }, "more than one field and value passed": { - setup: func() {}, - input: []string{"KEY", "field1", "value1", "field2", "value2"}, - output: []byte("-ERR wrong number of arguments for 'hsetnx' command\r\n"), + setup: func() {}, + input: []string{"KEY", "field1", "value1", "field2", "value2"}, + migratedOutput: EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSETNX"), + }, }, "key, field and value passed": { - setup: func() {}, - input: []string{"KEY1", "field_name", "value"}, - output: clientio.Encode(int64(1), false), + setup: func() {}, + input: []string{"KEY1", "field_name", "value"}, + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, "new set of key, field and value added": { - setup: func() {}, - input: []string{"KEY2", "field_name_new", "value_new_new"}, - output: clientio.Encode(int64(1), false), + setup: func() {}, + input: []string{"KEY2", "field_name_new", "value_new"}, + migratedOutput: EvalResponse{ + Result: int64(1), + Error: nil, + }, }, "apply with duplicate key, field and value names": { setup: func() { @@ -5499,14 +5598,37 @@ func testEvalHSETNX(t *testing.T, store *dstore.Store) { store.Put(key, obj) }, - input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value_2"}, - output: clientio.Encode(int64(0), false), + input: []string{"KEY_MOCK", "mock_field_name", "mock_field_value_2"}, + migratedOutput: EvalResponse{ + Result: int64(0), + Error: nil, + }, + }, + "key exists, field exists but value is nil": { + setup: func() { + key := "KEY_EXISTING" + field := "existing_field" + newMap := make(HashMap) + newMap[field] = "existing_value" + + obj := &object.Obj{ + TypeEncoding: object.ObjTypeHashMap | object.ObjEncodingHashMap, + Value: newMap, + LastAccessedAt: uint32(time.Now().Unix()), + } + + store.Put(key, obj) + }, + input: []string{"KEY_EXISTING", "existing_field", "new_value"}, + migratedOutput: EvalResponse{ + Result: int64(0), + Error: nil, + }, }, } - runEvalTests(t, tests, evalHSETNX, store) + runMigratedEvalTests(t, tests, evalHSETNX, store) } - func TestMSETConsistency(t *testing.T) { store := dstore.NewStore(nil, nil) evalMSET([]string{"KEY", "VAL", "KEY2", "VAL2"}, store) diff --git a/internal/eval/hmap.go b/internal/eval/hmap.go index de332eb7b..ccc507577 100644 --- a/internal/eval/hmap.go +++ b/internal/eval/hmap.go @@ -49,7 +49,7 @@ func hashMapBuilder(keyValuePairs []string, currentHashMap HashMap) (HashMap, in for iter <= argLength-1 { if iter >= argLength-1 || iter+1 > argLength-1 { - return hmap, -1, diceerrors.NewErr(fmt.Sprintf(diceerrors.ArityErr, "HSET")) + return hmap, -1, diceerrors.ErrWrongArgumentCount("HSET") } k := keyValuePairs[iter] @@ -65,27 +65,35 @@ func hashMapBuilder(keyValuePairs []string, currentHashMap HashMap) (HashMap, in return hmap, numKeysNewlySet, nil } -func getValueFromHashMap(key, field string, store *dstore.Store) (val, err []byte) { - var value string - +func getValueFromHashMap(key, field string, store *dstore.Store) *EvalResponse { obj := store.Get(key) - if obj == nil { - return clientio.RespNIL, nil + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } } - switch currentVal := obj.Value.(type) { - case HashMap: - val, present := currentVal.Get(field) - if !present { - return clientio.RespNIL, nil + hashMap, ok := obj.Value.(HashMap) + if !ok { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, } - value = *val - default: - return nil, diceerrors.NewErrWithFormattedMessage(diceerrors.WrongTypeErr) } - return clientio.Encode(value, false), nil + val, present := hashMap.Get(field) + if !present { + return &EvalResponse{ + Result: clientio.NIL, + Error: nil, + } + } + + return &EvalResponse{ + Result: *val, + Error: nil, + } } func (h HashMap) incrementValue(field string, increment int64) (int64, error) { diff --git a/internal/eval/hmap_test.go b/internal/eval/hmap_test.go index cba318717..62acca4ad 100644 --- a/internal/eval/hmap_test.go +++ b/internal/eval/hmap_test.go @@ -86,19 +86,21 @@ func TestGetValueFromHashMap(t *testing.T) { store.Put(key, obj) - val, err := getValueFromHashMap(key, field, store) - assert.Nil(t, err, "Expected no error when fetching an existing value from the hashmap") - assert.Equal(t, clientio.Encode("value1", false), val, "Expected value1 to be fetched for key1 and field1") - - // Fetching a non-existing field (should return RESP NIL) - val, err = getValueFromHashMap(key, "nonfield", store) - assert.Nil(t, err, "Expected no error when fetching a non-existing value from the hashmap") - assert.Equal(t, clientio.RespNIL, val, "Expected the value to give RespNIL") - - // Fetching a non-existing key (should return RESP NIL) - val, err = getValueFromHashMap("nonkey", field, store) - assert.Nil(t, err, "Expected no error when fetching a non-existing key from the hashmap") - assert.Equal(t, clientio.RespNIL, val, "Expected the value to give RespNIL") + // Test case: Fetching an existing field + response := getValueFromHashMap(key, field, store) + assert.Nil(t, response.Error, "Expected no error when fetching an existing value from the hashmap") + assert.NotNil(t, response.Result, "Expected a non-nil value to be fetched for key 'key1' and field 'field1'") + assert.Equal(t, value, response.Result, "Expected 'value1' to be fetched for key 'key1' and field 'field1'") + + // Test case: Fetching a non-existing field (should return clientio.NIL and no error) + response = getValueFromHashMap(key, "nonfield", store) + assert.Equal(t, clientio.NIL, response.Result, "Expected clientio.NIL for a non-existing field") + assert.Nil(t, response.Error, "Expected no error when fetching a non-existing field from the hashmap") + + // Test case: Fetching a non-existing key (should return clientio.NIL and ErrKeyNotFound) + response = getValueFromHashMap("nonkey", field, store) + assert.Equal(t, clientio.NIL, response.Result, "Expected clientio.NIL for a non-existing key") + assert.Nil(t, response.Error, "Expected no error for a non-existing key") } func TestHashMapIncrementFloatValue(t *testing.T) { diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index d69e460ce..06c036cb8 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -3482,3 +3482,255 @@ func evalGETDEL(args []string, store *dstore.Store) *EvalResponse { } } } + +// helper function to insert key value in hashmap associated with the given hash +func insertInHashMap(args []string, store *dstore.Store) (int64, error) { + key := args[0] + + obj := store.Get(key) + + var hashMap HashMap + + if obj != nil { + if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { + return 0, diceerrors.ErrWrongTypeOperation + } + hashMap = obj.Value.(HashMap) + } + + keyValuePairs := args[1:] + + hashMap, numKeys, err := hashMapBuilder(keyValuePairs, hashMap) + if err != nil { + return 0, err + } + + obj = store.NewObj(hashMap, -1, object.ObjTypeHashMap, object.ObjEncodingHashMap) + store.Put(key, obj) + + return numKeys, nil +} + +// evalHSET sets the specified fields to their +// respective values in a hashmap stored at key +// +// This command overwrites the values of specified +// fields that exist in the hash. +// +// If key doesn't exist, a new key holding a hash is created. +// +// Usage: HSET key field value [field value ...] +func evalHSET(args []string, store *dstore.Store) *EvalResponse { + if len(args) < 3 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSET"), + } + } + + numKeys, err := insertInHashMap(args, store) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: err, + } + } + + return &EvalResponse{ + Result: numKeys, + Error: nil, + } +} + +// evalHMSET sets the specified fields to their +// respective values in a hashmap stored at key +// +// This command overwrites the values of specified +// fields that exist in the hash. +// +// If key doesn't exist, a new key holding a hash is created. +// +// Usage: HMSET key field value [field value ...] +func evalHMSET(args []string, store *dstore.Store) *EvalResponse { + if len(args) < 3 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMSET"), + } + } + + _, err := insertInHashMap(args, store) + if err != nil { + return &EvalResponse{ + Result: nil, + Error: err, + } + } + + return &EvalResponse{ + Result: clientio.OK, + Error: nil, + } +} + +// evalHMGET returns an array of values associated with the given fields, +// in the same order as they are requested. +// If a field does not exist, returns a corresponding nil value in the array. +// If the key does not exist, returns an array of nil values. +func evalHMGET(args []string, store *dstore.Store) *EvalResponse { + if len(args) < 2 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HMGET"), + } + } + key := args[0] + + // Fetch the object from the store using the key + obj := store.Get(key) + + // Initialize the results slice + results := make([]interface{}, len(args[1:])) + + // If the object is nil, return empty results for all requested fields + if obj == nil { + for i := range results { + results[i] = nil // Return nil for non-existent fields + } + return &EvalResponse{ + Result: results, + Error: nil, + } + } + + // Assert that the object is of type HashMap + if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } + + hashMap := obj.Value.(HashMap) + + // Loop through the requested fields + for i, hmKey := range args[1:] { + hmValue, present := hashMap.Get(hmKey) + if present { + results[i] = *hmValue // Set the value if it exists + } else { + results[i] = nil // Set to nil if field does not exist + } + } + + // Return the results and no error + return &EvalResponse{ + Result: results, + Error: nil, + } +} + +func evalHGET(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 2 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HGET"), + } + } + + key := args[0] + hmKey := args[1] + + response := getValueFromHashMap(key, hmKey, store) + if response.Error != nil { + return &EvalResponse{ + Result: nil, + Error: response.Error, + } + } + + return &EvalResponse{ + Result: response.Result, + Error: nil, + } +} + +func evalHSETNX(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 3 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HSETNX"), + } + } + + key := args[0] + hmKey := args[1] + + response := getValueFromHashMap(key, hmKey, store) + if response.Error != nil { + return &EvalResponse{ + Result: nil, + Error: response.Error, + } + } + + if response.Result != clientio.NIL { + return &EvalResponse{ + Result: int64(0), + Error: nil, + } + } + + evalHSET(args, store) + + return &EvalResponse{ + Result: int64(1), + Error: nil, + } +} + +func evalHDEL(args []string, store *dstore.Store) *EvalResponse { + if len(args) < 2 { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongArgumentCount("HDEL"), + } + } + + key := args[0] + fields := args[1:] + + obj := store.Get(key) + + if obj == nil { + return &EvalResponse{ + Result: int64(0), + Error: nil, + } + } + + if err := object.AssertTypeAndEncoding(obj.TypeEncoding, object.ObjTypeHashMap, object.ObjEncodingHashMap); err != nil { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrWrongTypeOperation, + } + } + + hashMap := obj.Value.(HashMap) + count := int64(0) + for _, field := range fields { + if _, ok := hashMap[field]; ok { + delete(hashMap, field) + count++ + } + } + + if count > 0 { + store.Put(key, obj) + } + + return &EvalResponse{ + Result: count, + Error: nil, + } +} diff --git a/internal/server/cmd_meta.go b/internal/server/cmd_meta.go index 254d279d5..8be8af87d 100644 --- a/internal/server/cmd_meta.go +++ b/internal/server/cmd_meta.go @@ -287,6 +287,30 @@ var ( Cmd: "GETDEL", CmdType: SingleShard, } + hsetCmdMeta = CmdsMeta{ + Cmd: "HSET", + CmdType: SingleShard, + } + hgetCmdMeta = CmdsMeta{ + Cmd: "HGET", + CmdType: SingleShard, + } + hsetnxCmdMeta = CmdsMeta{ + Cmd: "HSETNX", + CmdType: SingleShard, + } + hdelCmdMeta = CmdsMeta{ + Cmd: "HDEL", + CmdType: SingleShard, + } + hmsetCmdMeta = CmdsMeta{ + Cmd: "HMSET", + CmdType: SingleShard, + } + hmgetCmdMeta = CmdsMeta{ + Cmd: "HMGET", + CmdType: SingleShard, + } // Metadata for multishard commands would go here. // These commands require both breakup and gather logic. @@ -371,6 +395,11 @@ func init() { WorkerCmdsMeta["CMS.MERGE"] = cmsMergeCmdMeta WorkerCmdsMeta["GETEX"] = getexCmdMeta WorkerCmdsMeta["GETDEL"] = getdelCmdMeta - + WorkerCmdsMeta["HSET"] = hsetCmdMeta + WorkerCmdsMeta["HGET"] = hgetCmdMeta + WorkerCmdsMeta["HSETNX"] = hsetnxCmdMeta + WorkerCmdsMeta["HDEL"] = hdelCmdMeta + WorkerCmdsMeta["HMSET"] = hmsetCmdMeta + WorkerCmdsMeta["HMGET"] = hmgetCmdMeta // Additional commands (multishard, custom) can be added here as needed. } diff --git a/internal/worker/cmd_meta.go b/internal/worker/cmd_meta.go index 4df6b2392..46a94e665 100644 --- a/internal/worker/cmd_meta.go +++ b/internal/worker/cmd_meta.go @@ -119,6 +119,12 @@ const ( CmdCMSInitByProb = "CMS.INITBYPROB" CmdCMSMerge = "CMS.MERGE" CmdCMSIncrBy = "CMS.INCRBY" + CmdHSet = "HSET" + CmdHGet = "HGET" + CmdHSetnx = "HSETNX" + CmdHDel = "HDEL" + CmdHMSet = "HMSET" + CmdHMGet = "HMGET" ) type CmdMeta struct { @@ -291,6 +297,24 @@ var CommandsMeta = map[string]CmdMeta{ CmdCMSMerge: { CmdType: SingleShard, }, + CmdHSet: { + CmdType: SingleShard, + }, + CmdHGet: { + CmdType: SingleShard, + }, + CmdHSetnx: { + CmdType: SingleShard, + }, + CmdHDel: { + CmdType: SingleShard, + }, + CmdHMSet: { + CmdType: SingleShard, + }, + CmdHMGet: { + CmdType: SingleShard, + }, // Custom commands. CmdAbort: { From d43577926873d0df0c8f189cdde6afa65c515ccb Mon Sep 17 00:00:00 2001 From: Jyotinder Singh Date: Mon, 4 Nov 2024 21:55:22 +0530 Subject: [PATCH 14/20] fix validateCmdMeta to handle Unwatch commands --- internal/worker/cmd_meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/worker/cmd_meta.go b/internal/worker/cmd_meta.go index 46a94e665..5c9ed2fb0 100644 --- a/internal/worker/cmd_meta.go +++ b/internal/worker/cmd_meta.go @@ -415,7 +415,7 @@ func validateCmdMeta(c string, meta CmdMeta) error { if meta.decomposeCommand == nil || meta.composeResponse == nil { return fmt.Errorf("multi-shard command %s must have both decomposeCommand and composeResponse implemented", c) } - case SingleShard, Watch, Custom: + case SingleShard, Watch, Unwatch, Custom: // No specific validations for these types currently default: return fmt.Errorf("unknown command type for %s", c) From e0a2f54bf2038b989a747f62a88ac1b753751f7f Mon Sep 17 00:00:00 2001 From: Arpit Bhayani Date: Mon, 4 Nov 2024 22:32:44 +0530 Subject: [PATCH 15/20] Made landing page, simple and dense --- docs/src/components/Hero.astro | 43 +++++++++++++--------------------- docs/src/components/Nav.astro | 6 ++--- docs/src/pages/index.astro | 27 ++++++++------------- docs/src/styles/main.scss | 4 ++-- 4 files changed, 31 insertions(+), 49 deletions(-) diff --git a/docs/src/components/Hero.astro b/docs/src/components/Hero.astro index de14d026e..1dc1e41dd 100644 --- a/docs/src/components/Hero.astro +++ b/docs/src/components/Hero.astro @@ -4,31 +4,27 @@ import Dice from "./Dice.astro"; import site from "../data/site.json"; --- -
-
-
-
- - We shipped DiceDB v0.0.4 - - -
- DiceDB Logo -
-
-

Build truly real-time applications

-

- DiceDB is an in-memory, real-time, and reactive database with Redis and SQL support optimized for modern hardware and building real-time applications. +

+
+
+
+

a super cache;

+

+ DiceDB is a redis-compliant, scalable, highly available, unified cache optimized for modern hardware + * +

+

+ docker run -p 7379:7379 dicedb/dicedb

-
+ -

- Want to evaluate and adopt DiceDB at your org? - block our calendar. -

-
-
-
-
- +

* DiceDB is a work in progress; track + roadmap and follow along.

diff --git a/docs/src/components/Nav.astro b/docs/src/components/Nav.astro index ade34a2f5..38f5a671e 100644 --- a/docs/src/components/Nav.astro +++ b/docs/src/components/Nav.astro @@ -36,19 +36,19 @@ import SocialHandlesIcons from "./SocialHandlesIcons.astro"; class={`navbar-item ${currentPage === "benchmarks" && "is-active"}`} href="/benchmarks" > - Benchmarks + Benchmarks - Docs + Docs - Blog + Blog