From 1e0625f28fdd29bc491d40e3d3d506b1eb48122c Mon Sep 17 00:00:00 2001 From: Sahil Date: Thu, 20 Jun 2024 01:10:58 +0530 Subject: [PATCH] feat: INCR command added --- echovault/api_generic.go | 17 +++++- echovault/api_generic_test.go | 5 +- internal/modules/generic/commands.go | 66 ++++++++++++++++++++--- internal/modules/generic/commands_test.go | 8 +-- internal/modules/generic/key_funcs.go | 10 ++++ 5 files changed, 93 insertions(+), 13 deletions(-) diff --git a/echovault/api_generic.go b/echovault/api_generic.go index 8ff600d..b342233 100644 --- a/echovault/api_generic.go +++ b/echovault/api_generic.go @@ -15,9 +15,10 @@ package echovault import ( - "github.com/echovault/echovault/internal" "strconv" "strings" + + "github.com/echovault/echovault/internal" ) // SetOptions modifies the behaviour for the Set command @@ -416,3 +417,17 @@ func (server *EchoVault) PExpireAt(key string, unixMilliseconds int, options PEx return internal.ParseIntegerResponse(b) } + +func (server *EchoVault) Incr(key string) (int, error) { + // Construct the command + cmd := []string{"INCR", key} + + // Execute the command + b, err := server.handleCommand(server.context, internal.EncodeCommand(cmd), nil, false, true) + if err != nil { + return 0, err + } + + // Parse the integer response + return internal.ParseIntegerResponse(b) +} diff --git a/echovault/api_generic_test.go b/echovault/api_generic_test.go index 848a44d..772b165 100644 --- a/echovault/api_generic_test.go +++ b/echovault/api_generic_test.go @@ -16,13 +16,14 @@ package echovault import ( "context" - "github.com/echovault/echovault/internal" - "github.com/echovault/echovault/internal/clock" "reflect" "slices" "strings" "testing" "time" + + "github.com/echovault/echovault/internal" + "github.com/echovault/echovault/internal/clock" ) func TestEchoVault_DEL(t *testing.T) { diff --git a/internal/modules/generic/commands.go b/internal/modules/generic/commands.go index 946ca75..c2dfcba 100644 --- a/internal/modules/generic/commands.go +++ b/internal/modules/generic/commands.go @@ -17,12 +17,13 @@ package generic import ( "errors" "fmt" - "github.com/echovault/echovault/internal" - "github.com/echovault/echovault/internal/constants" "log" "strconv" "strings" "time" + + "github.com/echovault/echovault/internal" + "github.com/echovault/echovault/internal/constants" ) type KeyObject struct { @@ -382,6 +383,50 @@ func handleExpireAt(params internal.HandlerFuncParams) ([]byte, error) { return []byte(":1\r\n"), nil } +func handleIncr(params internal.HandlerFuncParams) ([]byte, error) { + // Extract key from command + keys, err := incrKeyFunc(params.Command) + if err != nil { + return nil, err + } + + key := keys.WriteKeys[0] + currentValue, ok := params.GetValues(params.Context, []string{key})[key] + + var newValue int64 + var currentValueInt int64 + + // Check if the key exists and its current value + if !ok { + // If key does not exist, initialize it with 1 + newValue = 1 + } else { + // Use type switch to handle different types of currentValue + switch v := currentValue.(type) { + case string: + var err error + currentValueInt, err = strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, errors.New("value is not an integer or out of range") + } + case int: + currentValueInt = int64(v) + case int64: + currentValueInt = v + default: + return nil, errors.New("unexpected type for currentValue") + } + newValue = currentValueInt + 1 + } + + // 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 Commands() []internal.Command { return []internal.Command{ { @@ -389,7 +434,7 @@ func Commands() []internal.Command { Module: constants.GenericModule, Categories: []string{constants.WriteCategory, constants.SlowCategory}, Description: ` -(SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds]) +(SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds]) Set the value of a key, considering the value's type. NX - Only set if the key does not exist. XX - Only set if the key exists. @@ -442,7 +487,7 @@ PXAT - Expire at the exat time in unix milliseconds (positive integer).`, Command: "persist", Module: constants.GenericModule, Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory}, - Description: `(PERSIST key) Removes the TTl associated with a key, + Description: `(PERSIST key) Removes the TTl associated with a key, turning it from a volatile key to a persistent key.`, Sync: true, KeyExtractionFunc: persistKeyFunc, @@ -525,7 +570,7 @@ LT - Only set the expiry time if the new expiry time is less than the current on Module: constants.GenericModule, Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory}, Description: `(EXPIREAT key unix-time-seconds [NX | XX | GT | LT]) -Expire the key in at the exact unix time in seconds. +Expire the key in at the exact unix time in seconds. This commands turns a key into a volatile one. NX - Only set the expiry time if the key has no associated expiry. XX - Only set the expiry time if the key already has an expiry time. @@ -540,7 +585,7 @@ LT - Only set the expiry time if the new expiry time is less than the current on Module: constants.GenericModule, Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory}, Description: `(PEXPIREAT key unix-time-milliseconds [NX | XX | GT | LT]) -Expire the key in at the exact unix time in milliseconds. +Expire the key in at the exact unix time in milliseconds. This commands turns a key into a volatile one. NX - Only set the expiry time if the key has no associated expiry. XX - Only set the expiry time if the key already has an expiry time. @@ -550,5 +595,14 @@ LT - Only set the expiry time if the new expiry time is less than the current on KeyExtractionFunc: expireAtKeyFunc, HandlerFunc: handleExpireAt, }, + { + Command: "incr", + Module: constants.GenericModule, + Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory}, + Description: `(INCR key) Increments the number stored at key by one. 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 cannot be represented as integer. This operation is limited to 64 bit signed integers.`, + Sync: true, + KeyExtractionFunc: incrKeyFunc, + HandlerFunc: handleIncr, + }, } } diff --git a/internal/modules/generic/commands_test.go b/internal/modules/generic/commands_test.go index 9d091da..74e8973 100644 --- a/internal/modules/generic/commands_test.go +++ b/internal/modules/generic/commands_test.go @@ -17,15 +17,16 @@ package generic_test import ( "errors" "fmt" + "strings" + "testing" + "time" + "github.com/echovault/echovault/echovault" "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/clock" "github.com/echovault/echovault/internal/config" "github.com/echovault/echovault/internal/constants" "github.com/tidwall/resp" - "strings" - "testing" - "time" ) type KeyData struct { @@ -1893,5 +1894,4 @@ func Test_Generic(t *testing.T) { }) } }) - } diff --git a/internal/modules/generic/key_funcs.go b/internal/modules/generic/key_funcs.go index 875c34a..4a6a910 100644 --- a/internal/modules/generic/key_funcs.go +++ b/internal/modules/generic/key_funcs.go @@ -16,6 +16,7 @@ package generic import ( "errors" + "github.com/echovault/echovault/internal" "github.com/echovault/echovault/internal/constants" ) @@ -135,3 +136,12 @@ func expireAtKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) { WriteKeys: cmd[1:2], }, nil } + +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{ + WriteKeys: cmd[1:2], + }, nil +}