Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iss 57 implement RANDOMKEY command #94

Merged
merged 6 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,441 changes: 1,770 additions & 1,671 deletions coverage/coverage.out

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions echovault/api_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,3 +552,13 @@ func (server *EchoVault) Rename(oldKey string, newKey string) (string, error) {
// Parse the simple string response
return internal.ParseStringResponse(b)
}

// RandomKey returns a random key from the current active database.
// If no keys present in db returns an empty string.
func (server *EchoVault) RandomKey() (string, error) {
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"RANDOMKEY"}), nil, false, true)
if err != nil {
return "", err
}
return internal.ParseStringResponse(b)
}
34 changes: 34 additions & 0 deletions echovault/api_generic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1308,3 +1308,37 @@ func TestEchoVault_Rename(t *testing.T) {
})
}
}

func TestEchoVault_RANDOMKEY(t *testing.T) {
server := createEchoVault()

// test without keys
got, err := server.RandomKey()
if err != nil {
t.Error(err)
return
}
if got != "" {
t.Errorf("RANDOMKEY error, expected emtpy string (%v), got (%v)", []byte(""), []byte(got))
}

// test with keys
testkeys := []string{"key1", "key2", "key3"}
for _, k := range testkeys {
err := presetValue(server, context.Background(), k, "")
if err != nil {
t.Error(err)
return
}
}

actual, err := server.RandomKey()
if err != nil {
t.Error(err)
return
}
if !strings.Contains(actual, "key") {
t.Errorf("RANDOMKEY error, expected one of %v, got %s", testkeys, got)
}

}
26 changes: 26 additions & 0 deletions echovault/keyspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,29 @@ func (server *EchoVault) evictKeysWithExpiredTTL(ctx context.Context) error {

return nil
}

func (server *EchoVault) randomKey(ctx context.Context) string {
server.storeLock.RLock()
defer server.storeLock.RUnlock()

database := ctx.Value("Database").(int)

_max := len(server.store[database])
if _max == 0 {
return ""
}

randnum := rand.Intn(_max)
i := 0
var randkey string

for key, _ := range server.store[database] {
if i == randnum {
randkey = key
} else {
i++
}
}

return randkey
}
1 change: 1 addition & 0 deletions echovault/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (server *EchoVault) getHandlerFuncParams(ctx context.Context, cmd []string,
GetAllCommands: server.getCommands,
GetClock: server.getClock,
Flush: server.Flush,
Randomkey: server.randomKey,
SwapDBs: server.SwapDBs,
GetServerInfo: server.GetServerInfo,
DeleteKey: func(ctx context.Context, key string) error {
Expand Down
16 changes: 16 additions & 0 deletions internal/modules/generic/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,13 @@ func handleFlush(params internal.HandlerFuncParams) ([]byte, error) {
return []byte(constants.OkResponse), nil
}

func handleRandomkey(params internal.HandlerFuncParams) ([]byte, error) {

key := params.Randomkey(params.Context)

return []byte(fmt.Sprintf("+%v\r\n", key)), nil
}

func Commands() []internal.Command {
return []internal.Command{
{
Expand Down Expand Up @@ -957,5 +964,14 @@ Delete all the keys in the currently selected database. This command is always s
},
HandlerFunc: handleFlush,
},
{
Command: "randomkey",
Module: constants.GenericModule,
Categories: []string{constants.KeyspaceCategory, constants.ReadCategory, constants.SlowCategory},
Description: "(RANDOMKEY) Returns a random key from the current selected database.",
Sync: false,
KeyExtractionFunc: randomKeyFunc,
HandlerFunc: handleRandomkey,
},
}
}
42 changes: 41 additions & 1 deletion internal/modules/generic/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2021,7 +2021,6 @@ func Test_Generic(t *testing.T) {
t.Run("Test_HandlerDECR", func(t *testing.T) {
t.Parallel()
conn, err := internal.GetConnection("localhost", port)

if err != nil {
t.Error(err)
return
Expand Down Expand Up @@ -2757,4 +2756,45 @@ func Test_Generic(t *testing.T) {
}
}
})

t.Run("Test_HandleRANDOMKEY", func(t *testing.T) {
t.Parallel()

// Populate the store with keys first
for i := 0; i < 10; i++ {
_, _, err := mockServer.Set(
fmt.Sprintf("RandomKey%d", i),
fmt.Sprintf("Value%d", i),
echovault.SetOptions{},
)
if err != nil {
t.Error(err)
return
}
}

conn, err := internal.GetConnection("localhost", port)
if err != nil {
t.Error(err)
return
}
defer func() {
_ = conn.Close()
}()
client := resp.NewConn(conn)

expected := "Key"
if err = client.WriteArray([]resp.Value{resp.StringValue("RANDOMKEY")}); err != nil {
t.Error(err)
}

res, _, err := client.ReadValue()
if err != nil {
t.Error(err)
}

if !strings.Contains(res.String(), expected) {
t.Errorf("expected a key containing substring '%s', got %s", expected, res.String())
}
})
}
11 changes: 11 additions & 0 deletions internal/modules/generic/key_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,14 @@ func renameKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) {
WriteKeys: cmd[1:3],
}, nil
}

func randomKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) {
if len(cmd) != 1 {
return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse)
}
return internal.KeyExtractionFuncResult{
Channels: make([]string, 0),
ReadKeys: make([]string, 0),
WriteKeys: make([]string, 0),
}, nil
}
2 changes: 2 additions & 0 deletions internal/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ type HandlerFuncParams struct {
// FlushDB flushes the specified database keys. It accepts the integer index of the database to be flushed.
// If -1 is passed as the index, then all databases will be flushed.
Flush func(database int)
// Randomkey returns a random key
Randomkey func(ctx context.Context) string
}

// HandlerFunc is a functions described by a command where the bulk of the command handling is done.
Expand Down
Loading