Skip to content

Commit

Permalink
Go: HRANDFIELD.
Browse files Browse the repository at this point in the history
Signed-off-by: Yury-Fridlyand <[email protected]>
  • Loading branch information
Yury-Fridlyand committed Jan 18, 2025
1 parent 2ca1104 commit 21c110b
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
106 changes: 106 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,112 @@ func (client *baseClient) HScanWithOptions(
return handleScanResponse(result)
}

// Returns a random field name from the hash value stored at `key`.
//
// Since:
//
// Valkey 6.2.0 and above.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the hash.
//
// Return value:
//
// A random field name from the hash stored at `key`, or `nil` when
// the key does not exist.
//
// Example:
//
// field, err := client.HRandField("my_hash")
//
// [valkey.io]: https://valkey.io/commands/hexists/
func (client *baseClient) HRandField(key string) (Result[string], error) {
result, err := client.executeCommand(C.HRandField, []string{key})
if err != nil {
return CreateNilStringResult(), err
}
return handleStringOrNilResponse(result)
}

// Retrieves up to `count` random field names from the hash value stored at `key`.
//
// Since:
//
// Valkey 6.2.0 and above.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the hash.
// count - The number of field names to return.
// If `count` is positive, returns unique elements. If negative, allows for duplicates.
//
// Return value:
//
// An array of random field names from the hash stored at `key`,
// or an empty array when the key does not exist.
//
// Example:
//
// fields, err := client.HRandFieldWithCount("my_hash", -5)
//
// [valkey.io]: https://valkey.io/commands/hexists/
func (client *baseClient) HRandFieldWithCount(key string, count int64) ([]string, error) {
result, err := client.executeCommand(C.HRandField, []string{key, utils.IntToString(count)})
if err != nil {
return nil, err
}
// TODO remove that after merging with https://github.com/valkey-io/valkey-glide/pull/2965
data, err := handleStringArrayResponse(result)
var res []string
for _, val := range data {
res = append(res, val.Value())
}
return res, err
}

// Retrieves up to `count` random field names along with their values from the hash
// value stored at `key`.
//
// Since:
//
// Valkey 6.2.0 and above.
//
// See [valkey.io] for details.
//
// Parameters:
//
// key - The key of the hash.
// count - The number of field names to return.
// If `count` is positive, returns unique elements. If negative, allows for duplicates.
//
// Return value:
//
// A 2D `array` of `[field, value]` arrays, where `field` is a random
// field name from the hash and `value` is the associated value of the field name.
// If the hash does not exist or is empty, the response will be an empty array.
//
// Example:
//
// fieldsAndValues, err := client.HRandFieldWithCountWithValues("my_hash", -5)
// for _, pair := range fieldsAndValues {
// field := pair[0]
// value := pair[1]
// }
//
// [valkey.io]: https://valkey.io/commands/hexists/
func (client *baseClient) HRandFieldWithCountWithValues(key string, count int64) ([][]string, error) {
result, err := client.executeCommand(C.HRandField, []string{key, utils.IntToString(count), "WITHVALUES"})
if err != nil {
return nil, err
}
return handle2DStringArrayResponse(result)
}

func (client *baseClient) LPush(key string, elements []string) (int64, error) {
result, err := client.executeCommand(C.LPush, append([]string{key}, elements...))
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions go/api/hash_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,10 @@ type HashCommands interface {
//
// [valkey.io]: https://valkey.io/commands/hscan/
HScanWithOptions(key string, cursor string, options *options.HashScanOptions) (Result[string], []Result[string], error)

HRandField(key string) (Result[string], error)

HRandFieldWithCount(key string, count int64) ([]string, error)

HRandFieldWithCountWithValues(key string, count int64) ([][]string, error)
}
27 changes: 27 additions & 0 deletions go/api/response_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,33 @@ func handleStringArrayResponse(response *C.struct_CommandResponse) ([]Result[str
return convertStringArray(response)
}

func handle2DStringArrayResponse(response *C.struct_CommandResponse) ([][]string, error) {
defer C.free_command_response(response)
typeErr := checkResponseType(response, C.Array, false)
if typeErr != nil {
return nil, typeErr
}
array, err := parseArray(response)
if err != nil {
return nil, err
}
converted, err := arrayConverter[[]string]{
arrayConverter[string]{
nil,
false,
},
false,
}.convert(array)
if err != nil {
return nil, err
}
res, ok := converted.([][]string)
if !ok {
return nil, &RequestError{fmt.Sprintf("unexpected type: %T", converted)}
}
return res, nil
}

func handleStringArrayOrNullResponse(response *C.struct_CommandResponse) ([]Result[string], error) {
defer C.free_command_response(response)

Expand Down
71 changes: 71 additions & 0 deletions go/integTest/shared_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,77 @@ func (suite *GlideTestSuite) TestHScan() {
})
}

func (suite *GlideTestSuite) TestHRandField() {
suite.SkipIfServerVersionLowerThanBy("6.2.0")
suite.runWithDefaultClients(func(client api.BaseClient) {
key := uuid.NewString()

// key does not exist
res, err := client.HRandField(key)
assert.NoError(suite.T(), err)
assert.True(suite.T(), res.IsNil())
resc, err := client.HRandFieldWithCount(key, 5)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), resc)
rescv, err := client.HRandFieldWithCountWithValues(key, 5)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), rescv)

data := map[string]string{"f1": "v1", "f2": "v2", "f3": "v3"}
hset, err := client.HSet(key, data)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), int64(3), hset)

fields := make([]string, 0, len(data))
for k := range data {
fields = append(fields, k)
}
res, err = client.HRandField(key)
assert.NoError(suite.T(), err)
assert.Contains(suite.T(), fields, res.Value())

// With Count - positive count
resc, err = client.HRandFieldWithCount(key, 5)
assert.NoError(suite.T(), err)
assert.ElementsMatch(suite.T(), fields, resc)

// With Count - negative count
resc, err = client.HRandFieldWithCount(key, -5)
assert.NoError(suite.T(), err)
assert.Len(suite.T(), resc, 5)
for _, field := range resc {
assert.Contains(suite.T(), fields, field)
}

// With values - positive count
rescv, err = client.HRandFieldWithCountWithValues(key, 5)
assert.NoError(suite.T(), err)
resvMap := make(map[string]string)
for _, pair := range rescv {
resvMap[pair[0]] = pair[1]
}
assert.Equal(suite.T(), data, resvMap)

// With values - negative count
rescv, err = client.HRandFieldWithCountWithValues(key, -5)
assert.NoError(suite.T(), err)
assert.Len(suite.T(), resc, 5)
for _, pair := range rescv {
assert.Contains(suite.T(), fields, pair[0])
}

// key exists but holds non hash type value
key = uuid.NewString()
suite.verifyOK(client.Set(key, "HRandField"))
_, err = client.HRandField(key)
assert.IsType(suite.T(), &api.RequestError{}, err)
_, err = client.HRandFieldWithCount(key, 42)
assert.IsType(suite.T(), &api.RequestError{}, err)
_, err = client.HRandFieldWithCountWithValues(key, 42)
assert.IsType(suite.T(), &api.RequestError{}, err)
})
}

func (suite *GlideTestSuite) TestLPushLPop_WithExistingKey() {
suite.runWithDefaultClients(func(client api.BaseClient) {
list := []string{"value4", "value3", "value2", "value1"}
Expand Down

0 comments on commit 21c110b

Please sign in to comment.