From dbb0e1a16fa3cd74d483dfebc9a97f571d384368 Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Sun, 22 Dec 2024 23:49:25 +0530 Subject: [PATCH 1/6] LINDEX implementation added --- integration_tests/commands/http/deque_test.go | 76 +++++++++++++++++++ internal/eval/commands.go | 20 +++++ internal/eval/deque.go | 9 +++ internal/eval/eval_test.go | 42 ++++++++++ internal/eval/store_eval.go | 45 +++++++++++ internal/iothread/cmd_meta.go | 4 + 6 files changed, 196 insertions(+) diff --git a/integration_tests/commands/http/deque_test.go b/integration_tests/commands/http/deque_test.go index cb1582ce7..e179a4f11 100644 --- a/integration_tests/commands/http/deque_test.go +++ b/integration_tests/commands/http/deque_test.go @@ -599,3 +599,79 @@ func TestLPOPCount(t *testing.T) { exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"keys": [...]string{"k"}}}) } + +func TestLIndex(t *testing.T) { + exec := NewHTTPCommandExecutor() + exec.FireCommand(HTTPCommand{Command: "FLUSHDB"}) + + testcases := []struct { + name string + cmds []HTTPCommand + expects []any + }{ + { + name: "LINDEX for string values", + cmds: []HTTPCommand{ + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": "v1"}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": "v2"}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": "v3"}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": "v4"}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 0}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 2}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 3}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -1}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -4}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -3}}, + }, + expects: []interface{}{ + float64(1), + float64(2), + float64(3), + float64(4), + "v1", + "v3", + "v4", + "v4", + "v1", + "v2", + }, + }, + { + name: "LINDEX for int values", + cmds: []HTTPCommand{ + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": 1}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": 2}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": 3}}, + {Command: "RPUSH", Body: map[string]interface{}{"key": "k", "value": 4}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 0}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 2}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": 3}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -1}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -4}}, + {Command: "LINDEX", Body: map[string]interface{}{"key": "k", "value": -3}}, + }, + expects: []interface{}{ + float64(1), + float64(2), + float64(3), + float64(4), + 1, + 3, + 4, + 4, + 1, + 2, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + exec.FireCommand(HTTPCommand{Command: "DEL", Body: map[string]interface{}{"keys": []interface{}{"k"}}}) + for i, cmd := range tc.cmds { + result, _ := exec.FireCommand(cmd) + assert.Equal(t, tc.expects[i], result, "Value mismatch for cmd %v", cmd) + } + }) + } +} diff --git a/internal/eval/commands.go b/internal/eval/commands.go index 787bca20c..264ca64ba 100644 --- a/internal/eval/commands.go +++ b/internal/eval/commands.go @@ -1403,6 +1403,25 @@ var ( Arity: 4, KeySpecs: KeySpecs{BeginIndex: 1}, } + lindexCmdMeta = DiceCmdMeta { + Name: "LINDEX", + Info: ` + Usage: + LINDEX key index + Info: + Returns element stored at index in the list stored at a key. + Indexing is 0 based. + + Index can be negative. Negative index can be used to start from the end of the list i.e., index = -1 means last element of the list, index = -2 means second last element of the list and so on. + + Returns: + Element value present at that index. + `, + NewEval: evalLINDEX, + IsMigrated: true, + Arity: 2, + KeySpecs: KeySpecs{BeginIndex: 1}, + } ) func init() { @@ -1539,6 +1558,7 @@ func init() { DiceCmds["CMS.MERGE"] = cmsMergeCmdMeta DiceCmds["LINSERT"] = linsertCmdMeta DiceCmds["LRANGE"] = lrangeCmdMeta + DiceCmds["LINDEX"] = lindexCmdMeta DiceCmds["SINGLETOUCH"] = singleTouchCmdMeta DiceCmds["SINGLEDBSIZE"] = singleDBSizeCmdMeta diff --git a/internal/eval/deque.go b/internal/eval/deque.go index 59d755c5f..e80f6cf53 100644 --- a/internal/eval/deque.go +++ b/internal/eval/deque.go @@ -592,6 +592,15 @@ func (i *DequeIterator) Next() (string, error) { return x, nil } +func (i *DequeIterator) Value() (string, error) { + if i.ElementsTraversed == i.deque.Length { + return "", fmt.Errorf("iterator exhausted") + } + + x, _ := DecodeDeqEntry(i.CurrentNode.buf[i.BufIndex:]) + return x, nil +} + // *************************** deque entry encode/decode *************************** // EncodeDeqEntry encodes `x` into an entry of Deque. An entry will be encoded as [enc + data + backlen]. diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index fc6028e02..47662bbfa 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -97,6 +97,7 @@ func TestEval(t *testing.T) { testEvalRPOP(t, store) testEvalLLEN(t, store) testEvalLINSERT(t, store) + testEvalLINDEX(t, store) testEvalLRANGE(t, store) testEvalGETDEL(t, store) testEvalGETEX(t, store) @@ -9233,6 +9234,47 @@ func testEvalLINSERT(t *testing.T, store *dstore.Store) { runMigratedEvalTests(t, tests, evalLINSERT, store) } +func testEvalLINDEX(t *testing.T, store *dstore.Store) { + tests := map[string]evalTestCase{ + "nil value": { + input: nil, + migratedOutput: EvalResponse{Result: nil, Error: diceerrors.ErrWrongArgumentCount("LINDEX")}, + }, + "empty args": { + input: []string{}, + migratedOutput: EvalResponse{Result: nil, Error: diceerrors.ErrWrongArgumentCount("LINDEX")}, + }, + "wrong number of args": { + input: []string{"KEY1"}, + migratedOutput: EvalResponse{Result: nil, Error: diceerrors.ErrWrongArgumentCount("LINDEX")}, + }, + "key does not exist": { + input: []string{"NONEXISTENT_KEY", "1"}, + migratedOutput: EvalResponse{Result: nil, Error: diceerrors.ErrKeyDoesNotExist}, + }, + "key exists : positive index": { + setup: func() { + evalRPUSH([]string{"EXISTING_KEY", "mock_value1"}, store) + evalRPUSH([]string{"EXISTING_KEY", "mock_value2"}, store) + evalRPUSH([]string{"EXISTING_KEY", "mock_value3"}, store) + }, + input: []string{"EXISTING_KEY", "1"}, + migratedOutput: EvalResponse{Result: "mock_value2", Error: nil}, + }, + "key exists : negative index": { + setup: func() { + evalRPUSH([]string{"EXISTING_KEY", "mock_value1"}, store) + evalRPUSH([]string{"EXISTING_KEY", "mock_value2"}, store) + evalRPUSH([]string{"EXISTING_KEY", "mock_value3"}, store) + }, + input: []string{"EXISTING_KEY", "-1"}, + migratedOutput: EvalResponse{Result: "mock_value3", Error: nil}, + }, + } + + runMigratedEvalTests(t, tests, evalLINDEX, store) +} + func testEvalLRANGE(t *testing.T, store *dstore.Store) { tests := map[string]evalTestCase{ "nil value": { diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index d627c8b6f..f214683d1 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -5139,6 +5139,51 @@ func evalLINSERT(args []string, store *dstore.Store) *EvalResponse { return makeEvalResult(res) } +func evalLINDEX(args []string, store *dstore.Store) *EvalResponse { + if len(args) != 2 { + return makeEvalError(diceerrors.ErrWrongArgumentCount("LINDEX")) + } + + key := args[0] + index, err := strconv.ParseInt(args[1], 10, 64) + + if err != nil { + return makeEvalError(errors.New("-ERR value is not an integer or out of range")) + } + + obj := store.Get(key) + + if obj == nil { + return makeEvalError(diceerrors.ErrKeyDoesNotExist) + } + + if err := object.AssertType(obj.TypeEncoding, object.ObjTypeByteList); err != nil { + return makeEvalError(diceerrors.ErrWrongTypeOperation) + } + + if err := object.AssertEncoding(obj.TypeEncoding, object.ObjEncodingDeque); err != nil { + return makeEvalError(diceerrors.ErrWrongTypeOperation) + } + + deq := obj.Value.(*Deque) + + if index < 0 { + index = deq.Length - (-1 * index) + } + + var itr = deq.NewIterator() + var i int64 = 0 + for ; i < index; i++ { + itr.Next() + } + + value, _ := itr.Value() + return &EvalResponse{ + Result: value, + Error: nil, + } +} + // SETBIT key offset value func evalSETBIT(args []string, store *dstore.Store) *EvalResponse { var err error diff --git a/internal/iothread/cmd_meta.go b/internal/iothread/cmd_meta.go index bda33d29f..2f47630d1 100644 --- a/internal/iothread/cmd_meta.go +++ b/internal/iothread/cmd_meta.go @@ -129,6 +129,7 @@ const ( CmdGetDel = "GETDEL" CmdLrange = "LRANGE" CmdLinsert = "LINSERT" + CmdLindex = "LINDEX" CmdJSONArrAppend = "JSON.ARRAPPEND" CmdJSONArrLen = "JSON.ARRLEN" CmdJSONArrPop = "JSON.ARRPOP" @@ -396,6 +397,9 @@ var CommandsMeta = map[string]CmdMeta{ CmdLLEN: { CmdType: SingleShard, }, + CmdLindex: { + CmdType: SingleShard, + }, CmdCMSQuery: { CmdType: SingleShard, }, From 76bf1c89c7b8eeabd5df40eb9a631e6f97f1185c Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Wed, 25 Dec 2024 19:41:29 +0530 Subject: [PATCH 2/6] all tests are added --- integration_tests/commands/resp/deque_test.go | 33 +++++++++++++ .../commands/websocket/deque_test.go | 46 +++++++++++++++++++ internal/errors/migrated_errors.go | 1 + internal/eval/deque.go | 2 +- internal/eval/eval_test.go | 17 +++++-- internal/eval/store_eval.go | 33 +++++++------ 6 files changed, 111 insertions(+), 21 deletions(-) diff --git a/integration_tests/commands/resp/deque_test.go b/integration_tests/commands/resp/deque_test.go index d80263633..27cd043c5 100644 --- a/integration_tests/commands/resp/deque_test.go +++ b/integration_tests/commands/resp/deque_test.go @@ -24,6 +24,8 @@ import ( "testing" "time" + diceerrors "github.com/dicedb/dice/internal/errors" + "github.com/stretchr/testify/assert" ) @@ -501,6 +503,37 @@ func TestLInsert(t *testing.T) { deqCleanUp(conn, "k") } +func TestLIndex(t *testing.T) { + conn := getLocalConnection() + defer conn.Close() + + testcases := []struct { + name string + cmds []string + expect []any + }{ + { + name: "LINDEX from start", + cmds: []string{"RPUSH k v1 v2 v3 v4", "LINDEX k 0", "LINDEX k 1", "LINDEX k 2", "LINDEX k 3", "LINDEX k 4"}, + expect: []any{int64(4), "v1", "v2", "v3", "v4", diceerrors.ErrIndexOutOfRange.Error()}, + }, + { + name: "LINDEX from end", + cmds: []string{"LINDEX k -1", "LINDEX k -2", "LINDEX k -3", "LINDEX k -4", "LINDEX k -5"}, + expect: []any{"v4", "v3", "v2", "v1", diceerrors.ErrIndexOutOfRange.Error()}, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + for i, cmd := range tc.cmds { + result := FireCommand(conn, cmd) + assert.Equal(t, tc.expect[i], result) + } + }) + } +} + func TestLRange(t *testing.T) { conn := getLocalConnection() defer conn.Close() diff --git a/integration_tests/commands/websocket/deque_test.go b/integration_tests/commands/websocket/deque_test.go index 1b98e7452..b521bb196 100644 --- a/integration_tests/commands/websocket/deque_test.go +++ b/integration_tests/commands/websocket/deque_test.go @@ -127,6 +127,52 @@ func TestLPush(t *testing.T) { DeleteKey(t, conn, exec, "k") } +func TestLIndex(t *testing.T) { + exec := NewWebsocketCommandExecutor() + + testCases := []struct { + name string + cmds []string + expect []any + }{ + { + name: "LINDEX", + cmds: []string{ + "RPUSH k v1 v2 v3 v4", + "LINDEX k 0", + "LINDEX k 2", + "LINDEX k 3", + "LINDEX k -1", + "LINDEX k -4", + "LINDEX k -3", + }, + expect: []any{ + float64(4), + "v1", + "v3", + "v4", + "v4", + "v1", + "v2", + }, + }, + } + + conn := exec.ConnectToServer() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + for i, cmd := range tc.cmds { + result, err := exec.FireCommandAndReadResponse(conn, cmd) + assert.NilError(t, err) + assert.Equal(t, tc.expect[i], result, "Value mismatch for cmd %s", cmd) + } + }) + } + + DeleteKey(t, conn, exec, "k") +} + func TestRPush(t *testing.T) { deqNormalValues, deqEdgeValues := deqTestInit() exec := NewWebsocketCommandExecutor() diff --git a/internal/errors/migrated_errors.go b/internal/errors/migrated_errors.go index d456e1456..dd1d5b924 100644 --- a/internal/errors/migrated_errors.go +++ b/internal/errors/migrated_errors.go @@ -33,6 +33,7 @@ var ( ErrIntegerOutOfRange = errors.New("ERR value is not an integer or out of range") // Represents a value that is either not an integer or is out of allowed range. ErrInvalidNumberFormat = errors.New("ERR value is not an integer or a float") // Signals that a value provided is not in a valid integer or float format. ErrValueOutOfRange = errors.New("ERR value is out of range") // Indicates that a value is beyond the permissible range. + ErrIndexOutOfRange = errors.New("ERR Index out of range") // Indicates that index given by the user is out of range ErrOverflow = errors.New("ERR increment or decrement would overflow") // Signifies that an increment or decrement operation would exceed the limits. ErrSyntax = errors.New("ERR syntax error") // Represents a syntax error in a DiceDB command. ErrKeyNotFound = errors.New("ERR no such key") // Indicates that the specified key does not exist. diff --git a/internal/eval/deque.go b/internal/eval/deque.go index 549d4fa42..09e3cf3aa 100644 --- a/internal/eval/deque.go +++ b/internal/eval/deque.go @@ -612,7 +612,7 @@ func (i *DequeIterator) Next() (string, error) { func (i *DequeIterator) Value() (string, error) { if i.ElementsTraversed == i.deque.Length { - return "", fmt.Errorf("iterator exhausted") + return "(nil)", fmt.Errorf("iterator exhausted") } x, _ := DecodeDeqEntry(i.CurrentNode.buf[i.BufIndex:]) diff --git a/internal/eval/eval_test.go b/internal/eval/eval_test.go index 264c2e670..55c123ff7 100644 --- a/internal/eval/eval_test.go +++ b/internal/eval/eval_test.go @@ -9256,6 +9256,13 @@ func testEvalLINDEX(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "-1"}, migratedOutput: EvalResponse{Result: "mock_value3", Error: nil}, }, + "key exists : out of range index": { + setup: func() { + evalRPUSH([]string{"EXISTING_KEY", "mock_value1"}, store) + }, + input: []string{"EXISTING_KEY", "1"}, + migratedOutput: EvalResponse{Result: nil, Error: diceerrors.ErrIndexOutOfRange}, + }, } runMigratedEvalTests(t, tests, evalLINDEX, store) @@ -9308,7 +9315,7 @@ func testEvalLRANGE(t *testing.T, store *dstore.Store) { } func testEvalJSONARRINDEX(t *testing.T, store *dstore.Store) { - normalArray := `[0,1,2,3,4,3]` + normalArray := `[0,1,2,3,4,3]` tests := []evalTestCase{ { name: "nil value", @@ -9340,7 +9347,7 @@ func testEvalJSONARRINDEX(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "$", "3", "abc"}, migratedOutput: EvalResponse{ Result: nil, - Error: errors.New("ERR Couldn't parse as integer"), + Error: errors.New("ERR Couldn't parse as integer"), }, }, { @@ -9355,7 +9362,7 @@ func testEvalJSONARRINDEX(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "$", "3", "4", "abc"}, migratedOutput: EvalResponse{ Result: nil, - Error: errors.New("ERR Couldn't parse as integer"), + Error: errors.New("ERR Couldn't parse as integer"), }, }, { @@ -9370,7 +9377,7 @@ func testEvalJSONARRINDEX(t *testing.T, store *dstore.Store) { input: []string{"EXISTING_KEY", "$", "4", "4", "5"}, migratedOutput: EvalResponse{ Result: []interface{}{4}, - Error: nil, + Error: nil, }, }, } @@ -9392,4 +9399,4 @@ func testEvalJSONARRINDEX(t *testing.T, store *dstore.Store) { } }) } -} \ No newline at end of file +} diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index fa0857c49..410088f19 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -5061,11 +5061,7 @@ func evalLINDEX(args []string, store *dstore.Store) *EvalResponse { return makeEvalError(diceerrors.ErrKeyDoesNotExist) } - if err := object.AssertType(obj.TypeEncoding, object.ObjTypeByteList); err != nil { - return makeEvalError(diceerrors.ErrWrongTypeOperation) - } - - if err := object.AssertEncoding(obj.TypeEncoding, object.ObjEncodingDeque); err != nil { + if err := object.AssertType(obj.Type, object.ObjTypeDequeue); err != nil { return makeEvalError(diceerrors.ErrWrongTypeOperation) } @@ -5075,6 +5071,13 @@ func evalLINDEX(args []string, store *dstore.Store) *EvalResponse { index = deq.Length - (-1 * index) } + if index < 0 || index >= deq.Length { + return &EvalResponse{ + Result: nil, + Error: diceerrors.ErrIndexOutOfRange, + } + } + var itr = deq.NewIterator() var i int64 = 0 for ; i < index; i++ { @@ -7004,9 +7007,9 @@ func evalJSONARRINDEX(args []string, store *dstore.Store) *EvalResponse { adjustedStart, adjustedStop := adjustIndices(start, stop, length) - if adjustedStart == -1 { - arrIndexList = append(arrIndexList, -1) - continue + if adjustedStart == -1 { + arrIndexList = append(arrIndexList, -1) + continue } // Range [start, stop) : start is inclusive, stop is exclusive @@ -7029,18 +7032,18 @@ func evalJSONARRINDEX(args []string, store *dstore.Store) *EvalResponse { return makeEvalResult(arrIndexList) } -// adjustIndices adjusts the start and stop indices for array traversal. -// It handles negative indices and ensures they are within the array bounds. -func adjustIndices(start, stop, length int) (adjustedStart, adjustedStop int) { +// adjustIndices adjusts the start and stop indices for array traversal. +// It handles negative indices and ensures they are within the array bounds. +func adjustIndices(start, stop, length int) (adjustedStart, adjustedStop int) { if length == 0 { - return -1, -1 + return -1, -1 } if start < 0 { - start += length + start += length } - if stop <= 0 { - stop += length + if stop <= 0 { + stop += length } if start < 0 { start = 0 From 33b8343fbdf726e587fd419aee7ef0bb8f0e5c2a Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Wed, 25 Dec 2024 19:56:25 +0530 Subject: [PATCH 3/6] assigning iterator next return values --- internal/eval/store_eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/eval/store_eval.go b/internal/eval/store_eval.go index 728527e2f..74147b65c 100644 --- a/internal/eval/store_eval.go +++ b/internal/eval/store_eval.go @@ -5038,7 +5038,7 @@ func evalLINDEX(args []string, store *dstore.Store) *EvalResponse { var itr = deq.NewIterator() var i int64 = 0 for ; i < index; i++ { - itr.Next() + _, _ = itr.Next() } value, _ := itr.Value() From 15de25ee3a82c39fdddb8a03765b2c7c42ab7cae Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Thu, 26 Dec 2024 20:35:13 +0530 Subject: [PATCH 4/6] test case datatype changed --- integration_tests/commands/http/deque_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integration_tests/commands/http/deque_test.go b/integration_tests/commands/http/deque_test.go index aece807ac..cdda6c2e7 100644 --- a/integration_tests/commands/http/deque_test.go +++ b/integration_tests/commands/http/deque_test.go @@ -671,12 +671,12 @@ func TestLIndex(t *testing.T) { float64(2), float64(3), float64(4), - 1, - 3, - 4, - 4, - 1, - 2, + "1", + "3", + "4", + "4", + "1", + "2", }, }, } From 0e8a891bf4ea6618e61a07c56818afe3bcf81223 Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Thu, 26 Dec 2024 20:50:56 +0530 Subject: [PATCH 5/6] dequeu clean up added after LINDEX --- integration_tests/commands/resp/deque_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration_tests/commands/resp/deque_test.go b/integration_tests/commands/resp/deque_test.go index 27cd043c5..2baaa49c4 100644 --- a/integration_tests/commands/resp/deque_test.go +++ b/integration_tests/commands/resp/deque_test.go @@ -532,6 +532,8 @@ func TestLIndex(t *testing.T) { } }) } + + deqCleanUp(conn, "k") } func TestLRange(t *testing.T) { From af2547408a9ad44386a01ffa116ad50b5534e731 Mon Sep 17 00:00:00 2001 From: Arijit Roy Date: Sat, 28 Dec 2024 00:14:54 +0530 Subject: [PATCH 6/6] doc added for LINDEX --- docs/src/content/docs/commands/LINDEX.md | 89 ++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/src/content/docs/commands/LINDEX.md diff --git a/docs/src/content/docs/commands/LINDEX.md b/docs/src/content/docs/commands/LINDEX.md new file mode 100644 index 000000000..e554509f9 --- /dev/null +++ b/docs/src/content/docs/commands/LINDEX.md @@ -0,0 +1,89 @@ +--- +title: LINDEX +description: The `LINDEX` command in DiceDB is used to find an element present in the list stored at a key. +--- + +# LINDEX + +The `LINDEX` command in DiceDB is used to find an element present in the list stored at a key. If the key does not exist or the index specified is out of range of the list then the command will throw an error. + +## Command Syntax + +```bash +LINDEX [key] [index] +``` + +## Parameters + +| Parameter | Description | Type | Required | +| ------------ | ----------------------------------------------------------------------------------- | -------- | -------- | +| KEY | The key associated with the list for which the element you want to retrieve. | String | Yes | +| INDEX | The index or position of the element we want to retrieve in the list. 0-based indexing is used to consider elements from head or start of the list. Negative indexing is used (starts from -1) to consider elements from tail or end of the list. | Integer | Yes | + +## Return Values + +| Condition | Return Value | +| --------------------------------------------- | --------------------------------------------- | +| Command is successful | Returns the element present in that index | +| Key does not exist | error | +| Syntax or specified constraints are invalid | error | + +## Behaviour + +When the `LINDEX` command is executed, it performs the specified subcommand operation - + +- Returns element present at the `index` of the list associated with the `key` provided as arguments of the command. + +- If the `key` exists but is not associated with the list, an error is returned. + +## Errors + +- `Non existent key`: + + - Error Message: `ERR could not perform this operation on a key that doesn't exist` + +- `Missing Arguments`: + + - Error Message: `ERR wrong number of arguments for 'latency subcommand' command` + - If required arguments for a subcommand are missing, DiceDB will return an error. + +- `Key not holding a list` + + - Error Message : `WRONGTYPE Operation against a key holding the wrong kind of value` + +## Example Usage + +### Basic Usage + +``` +dicedb> LPUSH k 1 +1 +dicedb> LPUSH k 2 +2 +dicedb> LINDEX k 0 +2 +dicedb> LINDEX k -1 +1 +``` + +### Index out of range + +``` +dicedb> LINDEX k 3 +Error: ERR Index out of range +``` + +### Non-Existent Key + +``` +dicedb> LINDEX NON-EXISTENT -1 +Error: ERR could not perform this operation on a key that doesn't exist +``` + +## Best Practices + +- Check Key Type: Before using `LINDEX`, ensure that the key is associated with a list to avoid errors. + +- Handle Non-Existent Keys: Be prepared to handle the case where the key does not exist, as `LINDEX` will return `ERROR` in such scenarios. + +- Make sure the index is within range of the list. \ No newline at end of file