From 5433b88776cde7567c6d02bd6fc9470007547e9f Mon Sep 17 00:00:00 2001 From: Kelvin Mwinuka Date: Tue, 25 Jun 2024 12:28:23 +0800 Subject: [PATCH] Added command length tests for INCRBY command. Renamed INCRBY key func. Return WRONG_ARG_RESPONSE from key funcs when commands are not the correct length. --- echovault/api_generic.go | 9 ++-- internal/modules/generic/commands.go | 63 +++++++++++++++++++++++ internal/modules/generic/commands_test.go | 37 ++++++++----- internal/modules/generic/key_funcs.go | 13 ++++- 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/echovault/api_generic.go b/echovault/api_generic.go index 9fcb56f..2530592 100644 --- a/echovault/api_generic.go +++ b/echovault/api_generic.go @@ -467,11 +467,12 @@ func (server *EchoVault) Decr(key string) (int, error) { // If the value stored at the key is not an integer, an error is returned. // // Parameters: -// - `key` (string): The key whose value is to be incremented. -// - `increment` (int): The amount by which to increment the key's value. This can be a positive or negative integer. // -// Returns: -// - (int): The new value of the key after the increment operation. +// `key` - string - The key whose value is to be incremented. +// +// `increment` - int - The amount by which to increment the key's value. This can be a positive or negative integer. +// +// Returns: The new value of the key after the increment operation as an integer. func (server *EchoVault) IncrBy(key string, value string) (int, error) { // Construct the command cmd := []string{"DECRBY", key, value} diff --git a/internal/modules/generic/commands.go b/internal/modules/generic/commands.go index 52bad51..1527ed1 100644 --- a/internal/modules/generic/commands.go +++ b/internal/modules/generic/commands.go @@ -477,6 +477,58 @@ func handleDecr(params internal.HandlerFuncParams) ([]byte, error) { return []byte(fmt.Sprintf(":%d\r\n", newValue)), nil } +func handleIncrBy(params internal.HandlerFuncParams) ([]byte, error) { + // Extract key from command + keys, err := incrByKeyFunc(params.Command) + if err != nil { + return nil, err + } + + // Parse decrement value + decrValue, err := strconv.ParseInt(params.Command[2], 10, 64) + if err != nil { + return nil, errors.New("decrement value is not an integer or out of range") + } + + key := keys.WriteKeys[0] + values := params.GetValues(params.Context, []string{key}) // Get the current values for the specified keys + currentValue, ok := values[key] // Check if the key exists + + var newValue int64 + var currentValueInt int64 + + // Check if the key exists and its current value + if !ok || currentValue == nil { + // If key does not exist, initialize it with the decrement value + newValue = decrValue * -1 + } else { + // Use type switch to handle different types of currentValue + switch v := currentValue.(type) { + case string: + currentValueInt, err = strconv.ParseInt(v, 10, 64) // Parse the string to int64 + if err != nil { + return nil, errors.New("value is not an integer or out of range") + } + case int: + currentValueInt = int64(v) // Convert int to int64 + case int64: + currentValueInt = v // Use int64 value directly + default: + fmt.Printf("unexpected type for currentValue: %T\n", currentValue) + return nil, errors.New("unexpected type for currentValue") // Handle unexpected types + } + newValue = currentValueInt - decrValue // decrement the value by the specified amount + } + + // Set the new incremented value + if err := params.SetValues(params.Context, map[string]interface{}{key: fmt.Sprintf("%d", newValue)}); err != nil { + return nil, err + } + + // Prepare response with the actual new value + return []byte(fmt.Sprintf(":%d\r\n", newValue)), nil +} + func handleDecrBy(params internal.HandlerFuncParams) ([]byte, error) { // Extract key from command keys, err := decrByKeyFunc(params.Command) @@ -722,6 +774,17 @@ This operation is limited to 64 bit signed integers.`, KeyExtractionFunc: decrKeyFunc, HandlerFunc: handleDecr, }, + { + Command: "incrby", + Module: constants.GenericModule, + Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory}, + Description: `(INCRBY key increment) +Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. +An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer.`, + Sync: true, + KeyExtractionFunc: incrByKeyFunc, + HandlerFunc: handleIncrBy, + }, { Command: "decrby", Module: constants.GenericModule, diff --git a/internal/modules/generic/commands_test.go b/internal/modules/generic/commands_test.go index d85cd4a..28a9c3c 100644 --- a/internal/modules/generic/commands_test.go +++ b/internal/modules/generic/commands_test.go @@ -2195,20 +2195,29 @@ func Test_Generic(t *testing.T) { expectedResponse: 17, expectedError: nil, }, - // { - // name: "6. Command too long", - // key: "IncrByKey6", - // increment: "5", - // presetValue: nil, - // command: []resp.Value{ - // resp.StringValue("INCRBY"), - // resp.StringValue("IncrByKey6"), - // resp.StringValue("5"), - // resp.StringValue("extra_arg"), - // }, - // expectedResponse: 0, - // expectedError: errors.New("ERR wrong number of arguments for 'incrby' command"), - // }, + { + name: "5. Command too short", + key: "IncrByKey5", + increment: "5", + presetValue: nil, + command: []resp.Value{resp.StringValue("INCRBY"), resp.StringValue("IncrByKey5")}, + expectedResponse: 0, + expectedError: errors.New(constants.WrongArgsResponse), + }, + { + name: "6. Command too long", + key: "IncrByKey6", + increment: "5", + presetValue: nil, + command: []resp.Value{ + resp.StringValue("INCRBY"), + resp.StringValue("IncrByKey6"), + resp.StringValue("5"), + resp.StringValue("extra_arg"), + }, + expectedResponse: 0, + expectedError: errors.New(constants.WrongArgsResponse), + }, } for _, test := range tests { diff --git a/internal/modules/generic/key_funcs.go b/internal/modules/generic/key_funcs.go index a68d74e..9c5de68 100644 --- a/internal/modules/generic/key_funcs.go +++ b/internal/modules/generic/key_funcs.go @@ -139,7 +139,7 @@ func expireAtKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { func incrKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { if len(cmd) != 2 { - return internal.KeyExtractionFuncResult{}, errors.New("wrong number of arguments for INCR") + return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) } return internal.KeyExtractionFuncResult{ WriteKeys: cmd[1:2], @@ -148,13 +148,22 @@ func incrKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { func decrKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { if len(cmd) != 2 { - return internal.KeyExtractionFuncResult{}, errors.New("wrong number of arguments for INCR") + return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) } return internal.KeyExtractionFuncResult{ WriteKeys: cmd[1:2], }, nil } +func incrByKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { + if len(cmd) != 3 { + return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse) + } + return internal.KeyExtractionFuncResult{ + WriteKeys: []string{cmd[1]}, + }, nil +} + func decrByKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { if len(cmd) != 3 { return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse)