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

Go: Implementing List commands #2370

Merged
merged 3 commits into from
Oct 3, 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
86 changes: 85 additions & 1 deletion go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
type BaseClient interface {
StringCommands
HashCommands
ListCommands

// Close terminates the client by closing all associated resources.
Close()
Expand Down Expand Up @@ -135,7 +136,10 @@ func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) {
stringLengths := make([]C.ulong, len(args))
for i, str := range args {
bytes := utils.StringToBytes(str)
ptr := uintptr(unsafe.Pointer(&bytes[0]))
var ptr uintptr
janhavigupta007 marked this conversation as resolved.
Show resolved Hide resolved
if len(str) > 0 {
ptr = uintptr(unsafe.Pointer(&bytes[0]))
}
cStrings[i] = C.uintptr_t(ptr)
stringLengths[i] = C.size_t(len(str))
}
Expand Down Expand Up @@ -428,3 +432,83 @@ func (client *baseClient) HStrLen(key string, field string) (Result[int64], erro

return handleLongResponse(result)
}

func (client *baseClient) LPush(key string, elements []string) (Result[int64], error) {
result, err := client.executeCommand(C.LPush, append([]string{key}, elements...))
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongResponse(result)
}

func (client *baseClient) LPop(key string) (Result[string], error) {
result, err := client.executeCommand(C.LPop, []string{key})
if err != nil {
return CreateNilStringResult(), err
}

return handleStringOrNullResponse(result)
}

func (client *baseClient) LPopCount(key string, count int64) ([]Result[string], error) {
result, err := client.executeCommand(C.LPop, []string{key, utils.IntToString(count)})
if err != nil {
return nil, err
}

return handleStringArrayOrNullResponse(result)
}

func (client *baseClient) LPos(key string, element string) (Result[int64], error) {
result, err := client.executeCommand(C.LPos, []string{key, element})
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongOrNullResponse(result)
}

func (client *baseClient) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) {
result, err := client.executeCommand(C.LPos, append([]string{key, element}, options.toArgs()...))
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongOrNullResponse(result)
}

func (client *baseClient) LPosCount(key string, element string, count int64) ([]Result[int64], error) {
result, err := client.executeCommand(C.LPos, []string{key, element, CountKeyword, utils.IntToString(count)})
if err != nil {
return nil, err
}

return handleLongArrayResponse(result)
}

func (client *baseClient) LPosCountWithOptions(
key string,
element string,
count int64,
options *LPosOptions,
) ([]Result[int64], error) {
result, err := client.executeCommand(
C.LPos,
append([]string{key, element, CountKeyword, utils.IntToString(count)}, options.toArgs()...),
)
if err != nil {
return nil, err
}

return handleLongArrayResponse(result)
}

func (client *baseClient) RPush(key string, elements []string) (Result[int64], error) {
result, err := client.executeCommand(C.RPush, append([]string{key}, elements...))
if err != nil {
return CreateNilInt64Result(), err
}

return handleLongResponse(result)
}
58 changes: 57 additions & 1 deletion go/api/command_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

package api

import "strconv"
import (
"strconv"

"github.com/valkey-io/valkey-glide/go/glide/utils"
)

// SetOptions represents optional arguments for the [api.StringCommands.SetWithOptions] command.
//
Expand Down Expand Up @@ -147,3 +151,55 @@ const (
UnixMilliseconds ExpiryType = "PXAT" // expire the value after the Unix time specified by [api.Expiry.Count], in milliseconds
Persist ExpiryType = "PERSIST" // Remove the expiry associated with the key
)

// LPosOptions represents optional arguments for the [api.ListCommands.LPosWithOptions] and
// [api.ListCommands.LPosCountWithOptions] commands.
//
// See [valkey.io]
//
// [valkey.io]: https://valkey.io/commands/lpos/
type LPosOptions struct {
// Represents if the rank option is set.
IsRankSet bool
janhavigupta007 marked this conversation as resolved.
Show resolved Hide resolved
// The rank of the match to return.
Rank int64
// Represents if the max length parameter is set.
IsMaxLenSet bool
// The maximum number of comparisons to make between the element and the items in the list.
MaxLen int64
}

func NewLPosOptionsBuilder() *LPosOptions {
return &LPosOptions{}
}

func (lposOptions *LPosOptions) SetRank(rank int64) *LPosOptions {
lposOptions.IsRankSet = true
lposOptions.Rank = rank
return lposOptions
}

func (lposOptions *LPosOptions) SetMaxLen(maxLen int64) *LPosOptions {
lposOptions.IsMaxLenSet = true
lposOptions.MaxLen = maxLen
return lposOptions
}

func (opts *LPosOptions) toArgs() []string {
args := []string{}
if opts.IsRankSet {
args = append(args, RankKeyword, utils.IntToString(opts.Rank))
}

if opts.IsMaxLenSet {
args = append(args, MaxLenKeyword, utils.IntToString(opts.MaxLen))
}

return args
}

const (
CountKeyword string = "COUNT" // Valkey API keyword used to extract specific number of matching indices from a list.
RankKeyword string = "RANK" // Valkey API keyword use to determine the rank of the match to return.
MaxLenKeyword string = "MAXLEN" // Valkey API keyword used to determine the maximum number of list items to compare.
)
195 changes: 195 additions & 0 deletions go/api/list_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package api

// Supports commands and transactions for the "List Commands" group for standalone and cluster clients.
//
// See [valkey.io] for details.
//
// [valkey.io]: https://valkey.io/commands/?group=list
type ListCommands interface {
// Inserts all the specified values at the head of the list stored at key. elements are inserted one after the other to the
// head of the list, from the leftmost element to the rightmost element. If key does not exist, it is created as an empty
// list before performing the push operation.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The key of the list.
// elements - The elements to insert at the head of the list stored at key.
//
// Return value:
// A api.Result[int64] containing the length of the list after the push operation.
//
// For example:
// result, err := client.LPush("my_list", []string{"value1", "value2"})
// result.Value(): 2
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/lpush/
LPush(key string, elements []string) (Result[int64], error)

// Removes and returns the first elements of the list stored at key. The command pops a single element from the beginning
// of the list.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The key of the list.
//
// Return value:
// The Result[string] containing the value of the first element.
// If key does not exist, [api.CreateNilStringResult()] will be returned.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If key does not exist, [api.CreateNilStringResult()] will be returned.
// If key does not exist, `nil` will be returned.

For the end user it does not matter where null comes from. We should describe what returned and when.

Copy link
Collaborator Author

@janhavigupta007 janhavigupta007 Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, the api.CreateNilStringResult() is returning nil object, whereas it's returning api.Result[string]{val: "", isNil: true} object. To not make it too verbose, we decided to replace it with a function and link in the docs.

Please let me know if you think otherwise. Going ahead with the merge for now, I can address any change required in the next PR.

//
// For example:
// 1. result, err := client.LPush("my_list", []string{"value1", "value2"})
// value, err := client.LPop("my_list")
// value.Value(): "value2"
// result.IsNil(): false
// 2. result, err := client.LPop("non_existent")
// result.Value(): ""
// result.IsNil(); true
//
// [valkey.io]: https://valkey.io/commands/lpop/
LPop(key string) (Result[string], error)

// Removes and returns up to count elements of the list stored at key, depending on the list's length.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The key of the list.
// count - The count of the elements to pop from the list.
//
// Return value:
// An array of the popped elements as Result[string] will be returned depending on the list's length
// If key does not exist, nil will be returned.
//
// For example:
// 1. result, err := client.LPopCount("my_list", 2)
// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")}
// 2. result, err := client.LPopCount("non_existent")
// result: nil
//
// [valkey.io]: https://valkey.io/commands/lpop/
LPopCount(key string, count int64) ([]Result[string], error)

// Returns the index of the first occurrence of element inside the list specified by key. If no match is found,
// [api.CreateNilInt64Result()] is returned.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The name of the list.
// element - The value to search for within the list.
//
// Return value:
// The Result[int64] containing the index of the first occurrence of element, or [api.CreateNilInt64Result()] if element is
janhavigupta007 marked this conversation as resolved.
Show resolved Hide resolved
// not in the list.
//
// For example:
// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"})
// position, err := client.LPos("my_list", "e")
// position.Value(): 4
// position.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/lpos/
LPos(key string, element string) (Result[int64], error)

// Returns the index of an occurrence of element within a list based on the given options. If no match is found,
// [api.CreateNilInt64Result()] is returned.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The name of the list.
// element - The value to search for within the list.
// options - The LPos options.
//
// Return value:
// The Result[int64] containing the index of element, or [api.CreateNilInt64Result()] if element is not in the list.
//
// For example:
// 1. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"})
// result, err := client.LPosWithOptions("my_list", "e", api.NewLPosOptionsBuilder().SetRank(2))
// result.Value(): 5 (Returns the second occurrence of the element "e")
// 2. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"})
// result, err := client.LPosWithOptions("my_list", "e", api.NewLPosOptionsBuilder().SetRank(1).SetMaxLen(1000))
// result.Value(): 4
//
// [valkey.io]: https://valkey.io/commands/lpos/
LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error)

// Returns an array of indices of matching elements within a list.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The name of the list.
// element - The value to search for within the list.
// count - The number of matches wanted.
//
// Return value:
// An array that holds the indices of the matching elements within the list.
//
// For example:
// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"})
// result, err := client.LPosCount("my_list", "e", int64(3))
// result: []api.Result[int64]{api.CreateInt64Result(4), api.CreateInt64Result(5), api.CreateInt64Result(6)}
//
//
// [valkey.io]: https://valkey.io/commands/lpos/
LPosCount(key string, element string, count int64) ([]Result[int64], error)

// Returns an array of indices of matching elements within a list based on the given options. If no match is found, an
// empty array is returned.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The name of the list.
// element - The value to search for within the list.
// count - The number of matches wanted.
// options - The LPos options.
//
// Return value:
// An array that holds the indices of the matching elements within the list.
//
// For example:
// 1. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"})
// result, err := client.LPosWithOptions("my_list", "e", int64(1), api.NewLPosOptionsBuilder().SetRank(2))
// result: []api.Result[int64]{api.CreateInt64Result(5)}
// 2. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"})
// result, err := client.LPosWithOptions(
// "my_list",
// "e",
// int64(3),
// api.NewLPosOptionsBuilder().SetRank(2).SetMaxLen(1000),
// )
// result: []api.Result[int64]{api.CreateInt64Result(5), api.CreateInt64Result(6)}
//
//
// [valkey.io]: https://valkey.io/commands/lpos/
LPosCountWithOptions(key string, element string, count int64, options *LPosOptions) ([]Result[int64], error)

// Inserts all the specified values at the tail of the list stored at key.
// elements are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element.
// If key does not exist, it is created as an empty list before performing the push operation.
//
// See [valkey.io] for details.
//
// Parameters:
// key - The key of the list.
// elements - The elements to insert at the tail of the list stored at key.
//
// Return value:
// The Result[int64] containing the length of the list after the push operation.
//
// For example:
// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"})
// result.Value(): 7
// result.IsNil(): false
//
// [valkey.io]: https://valkey.io/commands/rpush/
RPush(key string, elements []string) (Result[int64], error)
}
Loading
Loading