diff --git a/go/api/base_client.go b/go/api/base_client.go index 4ded637c13..119feba60b 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -33,6 +33,7 @@ type BaseClient interface { ConnectionManagementCommands HyperLogLogCommands GenericBaseCommands + BitmapCommands // Close terminates the client by closing all associated resources. Close() } @@ -376,7 +377,7 @@ func (client *baseClient) HGet(key string, field string) (Result[string], error) return handleStringOrNilResponse(result) } -func (client *baseClient) HGetAll(key string) (map[Result[string]]Result[string], error) { +func (client *baseClient) HGetAll(key string) (map[string]string, error) { result, err := client.executeCommand(C.HGetAll, []string{key}) if err != nil { return nil, err @@ -565,6 +566,106 @@ 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/hrandfield/ +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/hrandfield/ +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 + } + return handleStringArrayResponse(result) +} + +// 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/hrandfield/ +func (client *baseClient) HRandFieldWithCountWithValues(key string, count int64) ([][]string, error) { + result, err := client.executeCommand(C.HRandField, []string{key, utils.IntToString(count), options.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 { @@ -672,7 +773,7 @@ func (client *baseClient) SUnionStore(destination string, keys []string) (int64, return handleIntResponse(result) } -func (client *baseClient) SMembers(key string) (map[Result[string]]struct{}, error) { +func (client *baseClient) SMembers(key string) (map[string]struct{}, error) { result, err := client.executeCommand(C.SMembers, []string{key}) if err != nil { return nil, err @@ -699,7 +800,7 @@ func (client *baseClient) SIsMember(key string, member string) (bool, error) { return handleBoolResponse(result) } -func (client *baseClient) SDiff(keys []string) (map[Result[string]]struct{}, error) { +func (client *baseClient) SDiff(keys []string) (map[string]struct{}, error) { result, err := client.executeCommand(C.SDiff, keys) if err != nil { return nil, err @@ -717,7 +818,7 @@ func (client *baseClient) SDiffStore(destination string, keys []string) (int64, return handleIntResponse(result) } -func (client *baseClient) SInter(keys []string) (map[Result[string]]struct{}, error) { +func (client *baseClient) SInter(keys []string) (map[string]struct{}, error) { result, err := client.executeCommand(C.SInter, keys) if err != nil { return nil, err @@ -782,7 +883,7 @@ func (client *baseClient) SMIsMember(key string, members []string) ([]bool, erro return handleBoolArrayResponse(result) } -func (client *baseClient) SUnion(keys []string) (map[Result[string]]struct{}, error) { +func (client *baseClient) SUnion(keys []string) (map[string]struct{}, error) { result, err := client.executeCommand(C.SUnion, keys) if err != nil { return nil, err @@ -1025,7 +1126,7 @@ func (client *baseClient) LPushX(key string, elements []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) LMPop(keys []string, listDirection ListDirection) (map[Result[string]][]Result[string], error) { +func (client *baseClient) LMPop(keys []string, listDirection ListDirection) (map[string][]string, error) { listDirectionStr, err := listDirection.toString() if err != nil { return nil, err @@ -1046,14 +1147,14 @@ func (client *baseClient) LMPop(keys []string, listDirection ListDirection) (map return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } func (client *baseClient) LMPopCount( keys []string, listDirection ListDirection, count int64, -) (map[Result[string]][]Result[string], error) { +) (map[string][]string, error) { listDirectionStr, err := listDirection.toString() if err != nil { return nil, err @@ -1074,14 +1175,14 @@ func (client *baseClient) LMPopCount( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } func (client *baseClient) BLMPop( keys []string, listDirection ListDirection, timeoutSecs float64, -) (map[Result[string]][]Result[string], error) { +) (map[string][]string, error) { listDirectionStr, err := listDirection.toString() if err != nil { return nil, err @@ -1102,7 +1203,7 @@ func (client *baseClient) BLMPop( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } func (client *baseClient) BLMPopCount( @@ -1110,7 +1211,7 @@ func (client *baseClient) BLMPopCount( listDirection ListDirection, count int64, timeoutSecs float64, -) (map[Result[string]][]Result[string], error) { +) (map[string][]string, error) { listDirectionStr, err := listDirection.toString() if err != nil { return nil, err @@ -1131,7 +1232,7 @@ func (client *baseClient) BLMPopCount( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } func (client *baseClient) LSet(key string, index int64, element string) (string, error) { @@ -1561,7 +1662,7 @@ func (client *baseClient) XReadWithOptions( // // Return value: // A `map[string]map[string][][]string` of stream keys to a map of stream entry IDs mapped to an array entries or `nil` if -// a key does not exist or does not contain requiested entries. +// a key does not exist or does not contain requested entries. // // For example: // @@ -1754,7 +1855,7 @@ func (client *baseClient) ZIncrBy(key string, increment float64, member string) return handleFloatResponse(result) } -func (client *baseClient) ZPopMin(key string) (map[Result[string]]Result[float64], error) { +func (client *baseClient) ZPopMin(key string) (map[string]float64, error) { result, err := client.executeCommand(C.ZPopMin, []string{key}) if err != nil { return nil, err @@ -1762,7 +1863,7 @@ func (client *baseClient) ZPopMin(key string) (map[Result[string]]Result[float64 return handleStringDoubleMapResponse(result) } -func (client *baseClient) ZPopMinWithCount(key string, count int64) (map[Result[string]]Result[float64], error) { +func (client *baseClient) ZPopMinWithCount(key string, count int64) (map[string]float64, error) { result, err := client.executeCommand(C.ZPopMin, []string{key, utils.IntToString(count)}) if err != nil { return nil, err @@ -1770,7 +1871,7 @@ func (client *baseClient) ZPopMinWithCount(key string, count int64) (map[Result[ return handleStringDoubleMapResponse(result) } -func (client *baseClient) ZPopMax(key string) (map[Result[string]]Result[float64], error) { +func (client *baseClient) ZPopMax(key string) (map[string]float64, error) { result, err := client.executeCommand(C.ZPopMax, []string{key}) if err != nil { return nil, err @@ -1778,7 +1879,7 @@ func (client *baseClient) ZPopMax(key string) (map[Result[string]]Result[float64 return handleStringDoubleMapResponse(result) } -func (client *baseClient) ZPopMaxWithCount(key string, count int64) (map[Result[string]]Result[float64], error) { +func (client *baseClient) ZPopMaxWithCount(key string, count int64) (map[string]float64, error) { result, err := client.executeCommand(C.ZPopMax, []string{key, utils.IntToString(count)}) if err != nil { return nil, err @@ -1892,7 +1993,7 @@ func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([] func (client *baseClient) ZRangeWithScores( key string, rangeQuery options.ZRangeQueryWithScores, -) (map[Result[string]]Result[float64], error) { +) (map[string]float64, error) { args := make([]string, 0, 10) args = append(args, key) args = append(args, rangeQuery.ToArgs()...) @@ -2504,7 +2605,7 @@ func (client *baseClient) XPendingWithOptions( return handleXPendingDetailResponse(result) } -// Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`. +// Creates a new consumer group uniquely identified by `group` for the stream stored at `key`. // // See [valkey.io] for details. // @@ -2531,7 +2632,7 @@ func (client *baseClient) XGroupCreate(key string, group string, id string) (str return client.XGroupCreateWithOptions(key, group, id, options.NewXGroupCreateOptions()) } -// Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`. +// Creates a new consumer group uniquely identified by `group` for the stream stored at `key`. // // See [valkey.io] for details. // @@ -2636,6 +2737,100 @@ func (client *baseClient) Echo(message string) (Result[string], error) { return handleStringOrNilResponse(result) } +// Destroys the consumer group `group` for the stream stored at `key`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name to delete. +// +// Return value: +// +// `true` if the consumer group is destroyed. Otherwise, `false`. +// +// Example: +// +// ok, err := client.XGroupDestroy("mystream", "mygroup") +// if !ok || err != nil { +// // handle errors +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-destroy/ +func (client *baseClient) XGroupDestroy(key string, group string) (bool, error) { + result, err := client.executeCommand(C.XGroupDestroy, []string{key, group}) + if err != nil { + return defaultBoolResponse, err + } + return handleBoolResponse(result) +} + +// Sets the last delivered ID for a consumer group. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// id - The stream entry ID that should be set as the last delivered ID for the consumer group. +// +// Return value: +// +// `"OK"`. +// +// Example: +// +// ok, err := client.XGroupSetId("mystream", "mygroup", "0-0") +// if ok != "OK" || err != nil { +// // handle error +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-create/ +func (client *baseClient) XGroupSetId(key string, group string, id string) (string, error) { + return client.XGroupSetIdWithOptions(key, group, id, options.NewXGroupSetIdOptionsOptions()) +} + +// Sets the last delivered ID for a consumer group. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// id - The stream entry ID that should be set as the last delivered ID for the consumer group. +// opts - The options for the command. See [options.XGroupSetIdOptions] for details. +// +// Return value: +// +// `"OK"`. +// +// Example: +// +// opts := options.NewXGroupSetIdOptionsOptions().SetEntriesRead(42) +// ok, err := client.XGroupSetIdWithOptions("mystream", "mygroup", "0-0", opts) +// if ok != "OK" || err != nil { +// // handle error +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-create/ +func (client *baseClient) XGroupSetIdWithOptions( + key string, + group string, + id string, + opts *options.XGroupSetIdOptions, +) (string, error) { + optionArgs, _ := opts.ToArgs() + args := append([]string{key, group, id}, optionArgs...) + result, err := client.executeCommand(C.XGroupSetId, args) + if err != nil { + return defaultStringResponse, err + } + return handleStringResponse(result) +} + // Removes all elements in the sorted set stored at `key` with a lexicographical order // between `rangeQuery.Start` and `rangeQuery.End`. // @@ -2645,7 +2840,6 @@ func (client *baseClient) Echo(message string) (Result[string], error) { // // key - The key of the sorted set. // rangeQuery - The range query object representing the minimum and maximum bound of the lexicographical range. -// can be an implementation of [options.LexBoundary]. // // Return value: // @@ -2939,6 +3133,184 @@ func (client *baseClient) XGroupDelConsumer( return handleIntResponse(result) } +// Returns the number of messages that were successfully acknowledged by the consumer group member +// of a stream. This command should be called on a pending message so that such message does not +// get processed again. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - he consumer group name. +// ids - Stream entry IDs to acknowledge and purge messages. +// +// Return value: +// +// The number of messages that were successfully acknowledged. +// +// Example: +// +// // Assuming streamId1 and streamId2 already exist. +// xackResult, err := client.XAck("key", "groupName", []string{"streamId1", "streamId2"}) +// fmt.Println(xackResult) // 2 +// +// [valkey.io]: https://valkey.io/commands/xack/ +func (client *baseClient) XAck(key string, group string, ids []string) (int64, error) { + result, err := client.executeCommand(C.XAck, append([]string{key, group}, ids...)) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Sets or clears the bit at offset in the string value stored at key. +// The offset is a zero-based index, with `0` being the first element of +// the list, `1` being the next element, and so on. The offset must be +// less than `2^32` and greater than or equal to `0` If a key is +// non-existent then the bit at offset is set to value and the preceding +// bits are set to `0`. +// +// Parameters: +// +// key - The key of the string. +// offset - The index of the bit to be set. +// value - The bit value to set at offset The value must be `0` or `1`. +// +// Return value: +// +// The bit value that was previously stored at offset. +// +// Example: +// +// result, err := client.SetBit("key", 1, 1) +// result: 1 +// +// [valkey.io]: https://valkey.io/commands/setbit/ +func (client *baseClient) SetBit(key string, offset int64, value int64) (int64, error) { + result, err := client.executeCommand(C.SetBit, []string{key, utils.IntToString(offset), utils.IntToString(value)}) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Returns the bit value at offset in the string value stored at key. +// +// offset should be greater than or equal to zero. +// +// Parameters: +// +// key - The key of the string. +// offset - The index of the bit to return. +// +// Return value: +// The bit at offset of the string. Returns zero if the key is empty or if the positive +// offset exceeds the length of the string. +// +// Example: +// +// result, err := client.GetBit("key1", 1, 1) +// result: 1 +// +// [valkey.io]: https://valkey.io/commands/getbit/ +func (client *baseClient) GetBit(key string, offset int64) (int64, error) { + result, err := client.executeCommand(C.GetBit, []string{key, utils.IntToString(offset)}) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Wait blocks the current client until all the previous write commands are successfully +// transferred and acknowledged by at least the specified number of replicas or if the timeout is reached, +// whichever is earlier +// +// Parameters: +// +// numberOfReplicas - The number of replicas to reach. +// timeout - The timeout value specified in milliseconds. A value of `0` will +// block indefinitely. +// +// Return value: +// The number of replicas reached by all the writes performed in the context of the current connection. +// +// Example: +// +// result, err := client.Wait(1, 1000) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: 1 // if cluster has 2 replicasets +// +// [valkey.io]: https://valkey.io/commands/wait/ +func (client *baseClient) Wait(numberOfReplicas int64, timeout int64) (int64, error) { + result, err := client.executeCommand(C.Wait, []string{utils.IntToString(numberOfReplicas), utils.IntToString(timeout)}) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Counts the number of set bits (population counting) in a string stored at key. +// +// Parameters: +// +// key - The key for the string to count the set bits of. +// +// Return value: +// The number of set bits in the string. Returns zero if the key is missing as it is +// treated as an empty string. +// +// Example: +// +// result, err := client.BitCount("mykey") +// result: 26 +// +// [valkey.io]: https://valkey.io/commands/bitcount/ +func (client *baseClient) BitCount(key string) (int64, error) { + result, err := client.executeCommand(C.BitCount, []string{key}) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Counts the number of set bits (population counting) in a string stored at key. The +// offsets start and end are zero-based indexes, with `0` being the first element of the +// list, `1` being the next element and so on. These offsets can also be negative numbers +// indicating offsets starting at the end of the list, with `-1` being the last element +// of the list, `-2` being the penultimate, and so on. +// +// Parameters: +// +// key - The key for the string to count the set bits of. +// options - The offset options - see [options.BitOffsetOptions]. +// +// Return value: +// The number of set bits in the string interval specified by start, end, and options. +// Returns zero if the key is missing as it is treated as an empty string. +// +// Example: +// +// opts := NewBitCountOptionsBuilder().SetStart(1).SetEnd(1).SetBitmapIndexType(options.BYTE) +// result, err := client.BitCount("mykey",options) +// result: 6 +// +// [valkey.io]: https://valkey.io/commands/bitcount/ +func (client *baseClient) BitCountWithOptions(key string, opts *options.BitCountOptions) (int64, error) { + optionArgs, err := opts.ToArgs() + if err != nil { + return defaultIntResponse, err + } + commandArgs := append([]string{key}, optionArgs...) + result, err := client.executeCommand(C.BitCount, commandArgs) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + // Copies the value stored at the source to the destination key if the // destination key does not yet exist. // diff --git a/go/api/bitmap_commands.go b/go/api/bitmap_commands.go new file mode 100644 index 0000000000..466df2e6c3 --- /dev/null +++ b/go/api/bitmap_commands.go @@ -0,0 +1,20 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package api + +import "github.com/valkey-io/valkey-glide/go/glide/api/options" + +// Supports commands and transactions for the "Bitmap" group of commands for standalone and cluster clients. +// +// See [valkey.io] for details. +// +// [valkey.io]: https://valkey.io/commands/#bitmap +type BitmapCommands interface { + SetBit(key string, offset int64, value int64) (int64, error) + + GetBit(key string, offset int64) (int64, error) + + BitCount(key string) (int64, error) + + BitCountWithOptions(key string, options *options.BitCountOptions) (int64, error) +} diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index 982876d6a5..63d4017940 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -704,6 +704,8 @@ type GenericBaseCommands interface { // [valkey.io]: https://valkey.io/commands/sort/ SortReadOnlyWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) + Wait(numberOfReplicas int64, timeout int64) (int64, error) + Copy(source string, destination string) (bool, error) CopyWithOptions(source string, destination string, option *CopyOptions) (bool, error) diff --git a/go/api/glide_client.go b/go/api/glide_client.go index a90575c767..51ea9ef4b4 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -51,7 +51,7 @@ func (client *glideClient) ConfigSet(parameters map[string]string) (string, erro return handleStringResponse(result) } -func (client *glideClient) ConfigGet(args []string) (map[Result[string]]Result[string], error) { +func (client *glideClient) ConfigGet(args []string) (map[string]string, error) { res, err := client.executeCommand(C.ConfigGet, args) if err != nil { return nil, err diff --git a/go/api/hash_commands.go b/go/api/hash_commands.go index ba2f248e8f..0d1eecac6e 100644 --- a/go/api/hash_commands.go +++ b/go/api/hash_commands.go @@ -46,14 +46,10 @@ type HashCommands interface { // // For example: // fieldValueMap, err := client.HGetAll("my_hash") - // // field1 equals api.CreateStringResult("field1") - // // value1 equals api.CreateStringResult("value1") - // // field2 equals api.CreateStringResult("field2") - // // value2 equals api.CreateStringResult("value2") - // // fieldValueMap equals map[api.Result[string]]api.Result[string]{field1: value1, field2: value2} + // // fieldValueMap equals map[string]string{field1: value1, field2: value2} // // [valkey.io]: https://valkey.io/commands/hgetall/ - HGetAll(key string) (map[Result[string]]Result[string], error) + HGetAll(key string) (map[string]string, error) // HMGet returns the values associated with the specified fields in the hash stored at key. // @@ -287,5 +283,11 @@ type HashCommands interface { HScan(key string, cursor string) (string, []string, error) + HRandField(key string) (Result[string], error) + + HRandFieldWithCount(key string, count int64) ([]string, error) + + HRandFieldWithCountWithValues(key string, count int64) ([][]string, error) + HScanWithOptions(key string, cursor string, options *options.HashScanOptions) (string, []string, error) } diff --git a/go/api/list_commands.go b/go/api/list_commands.go index d1c1970dfd..1d2942e5ae 100644 --- a/go/api/list_commands.go +++ b/go/api/list_commands.go @@ -491,10 +491,10 @@ type ListCommands interface { // For example: // result, err := client.LPush("my_list", []string{"one", "two", "three"}) // result, err := client.LMPop([]string{"my_list"}, api.Left) - // result[api.CreateStringResult("my_list")] = []api.Result[string]{api.CreateStringResult("three")} + // result["my_list"] = []string{"three"} // // [valkey.io]: https://valkey.io/commands/lmpop/ - LMPop(keys []string, listDirection ListDirection) (map[Result[string]][]Result[string], error) + LMPop(keys []string, listDirection ListDirection) (map[string][]string, error) // Pops one or more elements from the first non-empty list from the provided keys. // @@ -514,10 +514,10 @@ type ListCommands interface { // For example: // result, err := client.LPush("my_list", []string{"one", "two", "three"}) // result, err := client.LMPopCount([]string{"my_list"}, api.Left, int64(1)) - // result[api.CreateStringResult("my_list")] = []api.Result[string]{api.CreateStringResult("three")} + // result["my_list"] = []string{"three"} // // [valkey.io]: https://valkey.io/commands/lmpop/ - LMPopCount(keys []string, listDirection ListDirection, count int64) (map[Result[string]][]Result[string], error) + LMPopCount(keys []string, listDirection ListDirection, count int64) (map[string][]string, error) // Blocks the connection until it pops one element from the first non-empty list from the provided keys. BLMPop is the // blocking variant of [api.LMPop]. @@ -544,11 +544,11 @@ type ListCommands interface { // For example: // result, err := client.LPush("my_list", []string{"one", "two", "three"}) // result, err := client.BLMPop([]string{"my_list"}, api.Left, float64(0.1)) - // result[api.CreateStringResult("my_list")] = []api.Result[string]{api.CreateStringResult("three")} + // result["my_list"] = []string{"three"} // // [valkey.io]: https://valkey.io/commands/blmpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands - BLMPop(keys []string, listDirection ListDirection, timeoutSecs float64) (map[Result[string]][]Result[string], error) + BLMPop(keys []string, listDirection ListDirection, timeoutSecs float64) (map[string][]string, error) // Blocks the connection until it pops one or more elements from the first non-empty list from the provided keys. // BLMPopCount is the blocking variant of [api.LMPopCount]. @@ -576,7 +576,7 @@ type ListCommands interface { // For example: // result, err: client.LPush("my_list", []string{"one", "two", "three"}) // result, err := client.BLMPopCount([]string{"my_list"}, api.Left, int64(1), float64(0.1)) - // result[api.CreateStringResult("my_list")] = []api.Result[string]{api.CreateStringResult("three")} + // result["my_list"] = []string{"three"} // // [valkey.io]: https://valkey.io/commands/blmpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands @@ -585,7 +585,7 @@ type ListCommands interface { listDirection ListDirection, count int64, timeoutSecs float64, - ) (map[Result[string]][]Result[string], error) + ) (map[string][]string, error) // Sets the list element at index to element. // The index is zero-based, so 0 means the first element,1 the second element and so on. Negative indices can be used to diff --git a/go/api/options/bitcount_options.go b/go/api/options/bitcount_options.go new file mode 100644 index 0000000000..db68144c9a --- /dev/null +++ b/go/api/options/bitcount_options.go @@ -0,0 +1,63 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package options + +import ( + "github.com/valkey-io/valkey-glide/go/glide/utils" +) + +type BitmapIndexType string + +const ( + BYTE BitmapIndexType = "BYTE" + BIT BitmapIndexType = "BIT" +) + +// Optional arguments to `BitCount` in [BitMapCommands] +type BitCountOptions struct { + start *int64 + end *int64 + bitMapIndexType BitmapIndexType +} + +func NewBitCountOptionsBuilder() *BitCountOptions { + return &BitCountOptions{} +} + +// SetStart defines start byte to calculate bitcount in bitcount command. +func (options *BitCountOptions) SetStart(start int64) *BitCountOptions { + options.start = &start + return options +} + +// SetEnd defines start byte to calculate bitcount in bitcount command. +func (options *BitCountOptions) SetEnd(end int64) *BitCountOptions { + options.end = &end + return options +} + +// SetBitmapIndexType to specify start and end are in BYTE or BIT +func (options *BitCountOptions) SetBitmapIndexType(bitMapIndexType BitmapIndexType) *BitCountOptions { + options.bitMapIndexType = bitMapIndexType + return options +} + +// ToArgs converts the options to a list of arguments. +func (opts *BitCountOptions) ToArgs() ([]string, error) { + args := []string{} + var err error + + if opts.start != nil { + args = append(args, utils.IntToString(*opts.start)) + if opts.end != nil { + args = append(args, utils.IntToString(*opts.end)) + if opts.bitMapIndexType != "" { + if opts.bitMapIndexType == BIT || opts.bitMapIndexType == BYTE { + args = append(args, string(opts.bitMapIndexType)) + } + } + } + } + + return args, err +} diff --git a/go/api/options/constants.go b/go/api/options/constants.go index d2d4b594db..1b0e33d540 100644 --- a/go/api/options/constants.go +++ b/go/api/options/constants.go @@ -3,9 +3,10 @@ package options const ( - CountKeyword string = "COUNT" // Valkey API keyword used to extract specific number of matching indices from a list. - MatchKeyword string = "MATCH" // Valkey API keyword used to indicate the match filter. - NoValue string = "NOVALUE" // Valkey API keyword for the no value option for hcsan command. - WithScore string = "WITHSCORE" // Valkey API keyword for the with score option for zrank and zrevrank commands. - NoScores string = "NOSCORES" // Valkey API keyword for the no scores option for zscan command. + CountKeyword string = "COUNT" // Valkey API keyword used to extract specific number of matching indices from a list. + MatchKeyword string = "MATCH" // Valkey API keyword used to indicate the match filter. + NoValue string = "NOVALUE" // Valkey API keyword for the no value option for hcsan command. + WithScore string = "WITHSCORE" // Valkey API keyword for the with score option for zrank and zrevrank commands. + NoScores string = "NOSCORES" // Valkey API keyword for the no scores option for zscan command. + WithValues string = "WITHVALUES" // Valkey API keyword to query hash values along their names in `HRANDFIELD`. ) diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index cb27269f3b..71a76dc284 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -246,7 +246,6 @@ func (xpo *XPendingOptions) SetConsumer(consumer string) *XPendingOptions { func (xpo *XPendingOptions) ToArgs() ([]string, error) { args := []string{} - // if minIdleTime is set, we need to add an `IDLE` argument along with the minIdleTime if xpo.minIdleTime > 0 { args = append(args, "IDLE") args = append(args, utils.IntToString(xpo.minIdleTime)) @@ -280,9 +279,6 @@ func (xgco *XGroupCreateOptions) SetMakeStream() *XGroupCreateOptions { return xgco } -// A value representing the number of stream entries already read by the group. -// -// Since Valkey version 7.0.0. func (xgco *XGroupCreateOptions) SetEntriesRead(entriesRead int64) *XGroupCreateOptions { xgco.entriesRead = entriesRead return xgco @@ -302,3 +298,31 @@ func (xgco *XGroupCreateOptions) ToArgs() ([]string, error) { return args, nil } + +// Optional arguments for `XGroupSetId` in [StreamCommands] +type XGroupSetIdOptions struct { + entriesRead int64 +} + +// Create new empty `XGroupSetIdOptions` +func NewXGroupSetIdOptionsOptions() *XGroupSetIdOptions { + return &XGroupSetIdOptions{-1} +} + +// A value representing the number of stream entries already read by the group. +// +// Since Valkey version 7.0.0. +func (xgsio *XGroupSetIdOptions) SetEntriesRead(entriesRead int64) *XGroupSetIdOptions { + xgsio.entriesRead = entriesRead + return xgsio +} + +func (xgsio *XGroupSetIdOptions) ToArgs() ([]string, error) { + var args []string + + if xgsio.entriesRead > -1 { + args = append(args, "ENTRIESREAD", utils.IntToString(xgsio.entriesRead)) + } + + return args, nil +} diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 98ba2713d2..8f74a15132 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -186,6 +186,56 @@ func convertStringOrNilArray(response *C.struct_CommandResponse) ([]Result[strin return slice, nil } +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) + + typeErr := checkResponseType(response, C.Array, true) + if typeErr != nil { + return nil, typeErr + } + + if response.response_type == C.Null { + return nil, nil + } + + slice := make([]Result[string], 0, response.array_value_len) + for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { + res, err := convertCharArrayToString(&v, true) + if err != nil { + return nil, err + } + slice = append(slice, res) + } + return slice, nil +} + // array could be nillable, but strings - aren't func convertStringArray(response *C.struct_CommandResponse, isNilable bool) ([]string, error) { typeErr := checkResponseType(response, C.Array, isNilable) @@ -351,7 +401,7 @@ func handleBoolArrayResponse(response *C.struct_CommandResponse) ([]bool, error) return slice, nil } -func handleStringDoubleMapResponse(response *C.struct_CommandResponse) (map[Result[string]]Result[float64], error) { +func handleStringDoubleMapResponse(response *C.struct_CommandResponse) (map[string]float64, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Map, false) @@ -359,23 +409,26 @@ func handleStringDoubleMapResponse(response *C.struct_CommandResponse) (map[Resu return nil, typeErr } - m := make(map[Result[string]]Result[float64], response.array_value_len) - for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - key, err := convertCharArrayToString(v.map_key, true) - if err != nil { - return nil, err - } - typeErr := checkResponseType(v.map_value, C.Float, false) - if typeErr != nil { - return nil, typeErr - } - value := CreateFloat64Result(float64(v.map_value.float_value)) - m[key] = value + data, err := parseMap(response) + if err != nil { + return nil, err + } + aMap := data.(map[string]interface{}) + + converted, err := mapConverter[float64]{ + nil, false, + }.convert(aMap) + if err != nil { + return nil, err } - return m, nil + result, ok := converted.(map[string]float64) + if !ok { + return nil, &RequestError{fmt.Sprintf("unexpected type of map: %T", converted)} + } + return result, nil } -func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[Result[string]]Result[string], error) { +func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[string]string, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Map, false) @@ -383,25 +436,28 @@ func handleStringToStringMapResponse(response *C.struct_CommandResponse) (map[Re return nil, typeErr } - m := make(map[Result[string]]Result[string], response.array_value_len) - for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - key, err := convertCharArrayToString(v.map_key, true) - if err != nil { - return nil, err - } - value, err := convertCharArrayToString(v.map_value, true) - if err != nil { - return nil, err - } - m[key] = value + data, err := parseMap(response) + if err != nil { + return nil, err } + aMap := data.(map[string]interface{}) - return m, nil + converted, err := mapConverter[string]{ + nil, false, + }.convert(aMap) + if err != nil { + return nil, err + } + result, ok := converted.(map[string]string) + if !ok { + return nil, &RequestError{fmt.Sprintf("unexpected type of map: %T", converted)} + } + return result, nil } -func handleStringToStringArrayMapOrNullResponse( +func handleStringToStringArrayMapOrNilResponse( response *C.struct_CommandResponse, -) (map[Result[string]][]Result[string], error) { +) (map[string][]string, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Map, true) @@ -413,23 +469,28 @@ func handleStringToStringArrayMapOrNullResponse( return nil, nil } - m := make(map[Result[string]][]Result[string], response.array_value_len) - for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - key, err := convertCharArrayToString(v.map_key, true) - if err != nil { - return nil, err - } - value, err := convertStringOrNilArray(v.map_value) - if err != nil { - return nil, err - } - m[key] = value + data, err := parseMap(response) + if err != nil { + return nil, err } - return m, nil + converters := mapConverter[[]string]{ + arrayConverter[string]{}, + false, + } + + res, err := converters.convert(data) + if err != nil { + return nil, err + } + if result, ok := res.(map[string][]string); ok { + return result, nil + } + + return nil, &RequestError{fmt.Sprintf("unexpected type received: %T", res)} } -func handleStringSetResponse(response *C.struct_CommandResponse) (map[Result[string]]struct{}, error) { +func handleStringSetResponse(response *C.struct_CommandResponse) (map[string]struct{}, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Sets, false) @@ -437,13 +498,13 @@ func handleStringSetResponse(response *C.struct_CommandResponse) (map[Result[str return nil, typeErr } - slice := make(map[Result[string]]struct{}, response.sets_value_len) + slice := make(map[string]struct{}, response.sets_value_len) for _, v := range unsafe.Slice(response.sets_value, response.sets_value_len) { res, err := convertCharArrayToString(&v, true) if err != nil { return nil, err } - slice[res] = struct{}{} + slice[res.Value()] = struct{}{} } return slice, nil diff --git a/go/api/server_management_commands.go b/go/api/server_management_commands.go index 37954f543a..3653f17903 100644 --- a/go/api/server_management_commands.go +++ b/go/api/server_management_commands.go @@ -37,11 +37,11 @@ type ServerManagementCommands interface { // // For example: // result, err := client.ConfigGet([]string{"timeout" , "maxmemory"}) - // result[api.CreateStringResult("timeout")] = api.CreateStringResult("1000") - // result[api.CreateStringResult"maxmemory")] = api.CreateStringResult("1GB") + // // result["timeout"] = "1000" + // // result["maxmemory"] = "1GB" // // [valkey.io]: https://valkey.io/commands/config-get/ - ConfigGet(args []string) (map[Result[string]]Result[string], error) + ConfigGet(args []string) (map[string]string, error) // Sets configuration parameters to the specified values. // diff --git a/go/api/set_commands.go b/go/api/set_commands.go index 5d2315ae74..bed1d65200 100644 --- a/go/api/set_commands.go +++ b/go/api/set_commands.go @@ -54,20 +54,16 @@ type SetCommands interface { // key - The key from which to retrieve the set members. // // Return value: - // A map[Result[string]]struct{} containing all members of the set. - // Returns an empty map if key does not exist. + // A `map[string]struct{}` containing all members of the set. + // Returns an empty collection if key does not exist. // // For example: // // Assume set "my_set" contains: "member1", "member2" // result, err := client.SMembers("my_set") - // // result equals: - // // map[Result[string]]struct{}{ - // // api.CreateStringResult("member1"): {}, - // // api.CreateStringResult("member2"): {} - // // } + // // result: map[string]struct{}{ "member1": {}, "member2": {} } // // [valkey.io]: https://valkey.io/commands/smembers/ - SMembers(key string) (map[Result[string]]struct{}, error) + SMembers(key string) (map[string]struct{}, error) // SCard retrieves the set cardinality (number of elements) of the set stored at key. // @@ -119,19 +115,16 @@ type SetCommands interface { // keys - The keys of the sets to diff. // // Return value: - // A map[Result[string]]struct{} representing the difference between the sets. + // A `map[string]struct{}` representing the difference between the sets. // If a key does not exist, it is treated as an empty set. // // Example: // result, err := client.SDiff([]string{"set1", "set2"}) - // // result might contain: - // // map[Result[string]]struct{}{ - // // api.CreateStringResult("element"): {}, - // // } + // // result: map[string]struct{}{ "element": {} } // // Indicates that "element" is present in "set1", but missing in "set2" // // [valkey.io]: https://valkey.io/commands/sdiff/ - SDiff(keys []string) (map[Result[string]]struct{}, error) + SDiff(keys []string) (map[string]struct{}, error) // SDiffStore stores the difference between the first set and all the successive sets in keys // into a new set at destination. @@ -165,20 +158,16 @@ type SetCommands interface { // keys - The keys of the sets to intersect. // // Return value: - // A map[Result[string]]struct{} containing members which are present in all given sets. - // If one or more sets do not exist, an empty map will be returned. - // + // A `map[string]struct{}` containing members which are present in all given sets. + // If one or more sets do not exist, an empty collection will be returned. // // Example: // result, err := client.SInter([]string{"set1", "set2"}) - // // result might contain: - // // map[Result[string]]struct{}{ - // // api.CreateStringResult("element"): {}, - // // } + // // result: map[string]struct{}{ "element": {} } // // Indicates that "element" is present in both "set1" and "set2" // // [valkey.io]: https://valkey.io/commands/sinter/ - SInter(keys []string) (map[Result[string]]struct{}, error) + SInter(keys []string) (map[string]struct{}, error) // Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination` // @@ -353,9 +342,8 @@ type SetCommands interface { // keys - The keys of the sets. // // Return value: - // A map[Result[string]]struct{} of members which are present in at least one of the given sets. - // If none of the sets exist, an empty map will be returned. - // + // A `map[string]struct{}` of members which are present in at least one of the given sets. + // If none of the sets exist, an empty collection will be returned. // // Example: // result1, err := client.SAdd("my_set1", []string {"member1", "member2"}) @@ -367,15 +355,15 @@ type SetCommands interface { // // result.IsNil(): false // // result3, err := client.SUnion([]string {"my_set1", "my_set2"}) - // // result3.Value(): "{'member1', 'member2', 'member3'}" + // // result3: "{'member1', 'member2', 'member3'}" // // err: nil // // result4, err := client.SUnion([]string {"my_set1", "non_existing_set"}) - // // result4.Value(): "{'member1', 'member2'}" + // // result4: "{'member1', 'member2'}" // // err: nil // // [valkey.io]: https://valkey.io/commands/sunion/ - SUnion(keys []string) (map[Result[string]]struct{}, error) + SUnion(keys []string) (map[string]struct{}, error) SScan(key string, cursor string) (string, []string, error) diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index 4010d62d05..62d06091bd 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -127,10 +127,10 @@ type SortedSetCommands interface { // // Example: // res, err := client.zpopmin("mySortedSet") - // fmt.Println(res.Value()) // Output: map["member1":5.0] + // fmt.Println(res) // Output: map["member1": 5.0] // // [valkey.io]: https://valkey.io/commands/zpopmin/ - ZPopMin(key string) (map[Result[string]]Result[float64], error) + ZPopMin(key string) (map[string]float64, error) // Removes and returns up to `count` members with the lowest scores from the sorted set // stored at the specified `key`. @@ -148,10 +148,10 @@ type SortedSetCommands interface { // // Example: // res, err := client.ZPopMinWithCount("mySortedSet", 2) - // fmt.Println(res.Value()) // Output: map["member1":5.0, "member2":6.0] + // fmt.Println(res) // Output: map["member1": 5.0, "member2": 6.0] // // [valkey.io]: https://valkey.io/commands/zpopmin/ - ZPopMinWithCount(key string, count int64) (map[Result[string]]Result[float64], error) + ZPopMinWithCount(key string, count int64) (map[string]float64, error) // Removes and returns the member with the highest score from the sorted set stored at the // specified `key`. @@ -168,10 +168,10 @@ type SortedSetCommands interface { // // Example: // res, err := client.zpopmax("mySortedSet") - // fmt.Println(res.Value()) // Output: map["member2":8.0] + // fmt.Println(res) // Output: map["member2": 8.0] // // [valkey.io]: https://valkey.io/commands/zpopmin/ - ZPopMax(key string) (map[Result[string]]Result[float64], error) + ZPopMax(key string) (map[string]float64, error) // Removes and returns up to `count` members with the highest scores from the sorted set // stored at the specified `key`. @@ -189,10 +189,10 @@ type SortedSetCommands interface { // // Example: // res, err := client.ZPopMaxWithCount("mySortedSet", 2) - // fmt.Println(res.Value()) // Output: map["member1":5.0, "member2":6.0] + // fmt.Println(res) // Output: map["member1": 5.0, "member2": 6.0] // // [valkey.io]: https://valkey.io/commands/zpopmin/ - ZPopMaxWithCount(key string, count int64) (map[Result[string]]Result[float64], error) + ZPopMaxWithCount(key string, count int64) (map[string]float64, error) // Removes the specified members from the sorted set stored at `key`. // Specified members that are not a member of this set are ignored. @@ -266,7 +266,7 @@ type SortedSetCommands interface { ZRange(key string, rangeQuery options.ZRangeQuery) ([]string, error) - ZRangeWithScores(key string, rangeQuery options.ZRangeQueryWithScores) (map[Result[string]]Result[float64], error) + ZRangeWithScores(key string, rangeQuery options.ZRangeQueryWithScores) (map[string]float64, error) // Returns the rank of `member` in the sorted set stored at `key`, with // scores ordered from low to high, starting from `0`. diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index b5febb245d..0dc369482a 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -149,11 +149,19 @@ type StreamCommands interface { XPendingWithOptions(key string, group string, options *options.XPendingOptions) ([]XPendingDetail, error) + XGroupSetId(key string, group string, id string) (string, error) + + XGroupSetIdWithOptions(key string, group string, id string, opts *options.XGroupSetIdOptions) (string, error) + XGroupCreate(key string, group string, id string) (string, error) XGroupCreateWithOptions(key string, group string, id string, opts *options.XGroupCreateOptions) (string, error) + XGroupDestroy(key string, group string) (bool, error) + XGroupCreateConsumer(key string, group string, consumer string) (bool, error) XGroupDelConsumer(key string, group string, consumer string) (int64, error) + + XAck(key string, group string, ids []string) (int64, error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 3a27255cc2..06364f2021 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3,6 +3,7 @@ package integTest import ( + "fmt" "math" "reflect" "strconv" @@ -635,9 +636,14 @@ func (suite *GlideTestSuite) TestHSet_WithExistingKey() { func (suite *GlideTestSuite) TestHSet_byteString() { suite.runWithDefaultClients(func(client api.BaseClient) { + field1 := string([]byte{0xFF, 0x00, 0xAA}) + value1 := string([]byte{0xDE, 0xAD, 0xBE, 0xEF}) + field2 := string([]byte{0x01, 0x02, 0x03, 0xFE}) + value2 := string([]byte{0xCA, 0xFE, 0xBA, 0xBE}) + fields := map[string]string{ - string([]byte{0xFF, 0x00, 0xAA}): string([]byte{0xDE, 0xAD, 0xBE, 0xEF}), - string([]byte{0x01, 0x02, 0x03, 0xFE}): string([]byte{0xCA, 0xFE, 0xBA, 0xBE}), + field1: value1, + field2: value2, } key := string([]byte{0x01, 0x02, 0x03, 0xFE}) @@ -646,16 +652,8 @@ func (suite *GlideTestSuite) TestHSet_byteString() { assert.Equal(suite.T(), int64(2), res1) res2, err := client.HGetAll(key) - key1 := api.CreateStringResult(string([]byte{0xFF, 0x00, 0xAA})) - value1 := api.CreateStringResult(string([]byte{0xDE, 0xAD, 0xBE, 0xEF})) - key2 := api.CreateStringResult(string([]byte{0x01, 0x02, 0x03, 0xFE})) - value2 := api.CreateStringResult(string([]byte{0xCA, 0xFE, 0xBA, 0xBE})) - fieldsResult := map[api.Result[string]]api.Result[string]{ - key1: value1, - key2: value2, - } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), fieldsResult, res2) + assert.Equal(suite.T(), fields, res2) }) } @@ -728,14 +726,9 @@ func (suite *GlideTestSuite) TestHGetAll_WithExistingKey() { assert.Nil(suite.T(), err) assert.Equal(suite.T(), int64(2), res1) - field1 := api.CreateStringResult("field1") - value1 := api.CreateStringResult("value1") - field2 := api.CreateStringResult("field2") - value2 := api.CreateStringResult("value2") - fieldsResult := map[api.Result[string]]api.Result[string]{field1: value1, field2: value2} res2, err := client.HGetAll(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), fieldsResult, res2) + assert.Equal(suite.T(), fields, res2) }) } @@ -818,10 +811,8 @@ func (suite *GlideTestSuite) TestHSetNX_WithNotExistingKey() { assert.True(suite.T(), res1) res2, err := client.HGetAll(key) - field1 := api.CreateStringResult("field1") - value1 := api.CreateStringResult("value1") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{field1: value1}, res2) + assert.Equal(suite.T(), map[string]string{"field1": "value1"}, res2) }) } @@ -1276,6 +1267,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"} @@ -1568,21 +1630,21 @@ func (suite *GlideTestSuite) TestSUnionStore() { memberArray1 := []string{"a", "b", "c"} memberArray2 := []string{"c", "d", "e"} memberArray3 := []string{"e", "f", "g"} - expected1 := map[api.Result[string]]struct{}{ - api.CreateStringResult("a"): {}, - api.CreateStringResult("b"): {}, - api.CreateStringResult("c"): {}, - api.CreateStringResult("d"): {}, - api.CreateStringResult("e"): {}, + expected1 := map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, } - expected2 := map[api.Result[string]]struct{}{ - api.CreateStringResult("a"): {}, - api.CreateStringResult("b"): {}, - api.CreateStringResult("c"): {}, - api.CreateStringResult("d"): {}, - api.CreateStringResult("e"): {}, - api.CreateStringResult("f"): {}, - api.CreateStringResult("g"): {}, + expected2 := map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, + "f": {}, + "g": {}, } t := suite.T() @@ -1771,9 +1833,7 @@ func (suite *GlideTestSuite) TestSDiff() { result, err := client.SDiff([]string{key1, key2}) assert.Nil(suite.T(), err) - assert.Len(suite.T(), result, 2) - assert.Contains(suite.T(), result, api.CreateStringResult("a")) - assert.Contains(suite.T(), result, api.CreateStringResult("b")) + assert.Equal(suite.T(), map[string]struct{}{"a": {}, "b": {}}, result) }) } @@ -1799,10 +1859,7 @@ func (suite *GlideTestSuite) TestSDiff_WithSingleKeyExist() { res2, err := client.SDiff([]string{key1, key2}) assert.Nil(suite.T(), err) - assert.Len(suite.T(), res2, 3) - assert.Contains(suite.T(), res2, api.CreateStringResult("a")) - assert.Contains(suite.T(), res2, api.CreateStringResult("b")) - assert.Contains(suite.T(), res2, api.CreateStringResult("c")) + assert.Equal(suite.T(), map[string]struct{}{"a": {}, "b": {}, "c": {}}, res2) }) } @@ -1826,9 +1883,7 @@ func (suite *GlideTestSuite) TestSDiffStore() { members, err := client.SMembers(key3) assert.Nil(suite.T(), err) - assert.Len(suite.T(), members, 2) - assert.Contains(suite.T(), members, api.CreateStringResult("a")) - assert.Contains(suite.T(), members, api.CreateStringResult("b")) + assert.Equal(suite.T(), map[string]struct{}{"a": {}, "b": {}}, members) }) } @@ -1863,9 +1918,7 @@ func (suite *GlideTestSuite) TestSinter() { members, err := client.SInter([]string{key1, key2}) assert.Nil(suite.T(), err) - assert.Len(suite.T(), members, 2) - assert.Contains(suite.T(), members, api.CreateStringResult("c")) - assert.Contains(suite.T(), members, api.CreateStringResult("d")) + assert.Equal(suite.T(), map[string]struct{}{"c": {}, "d": {}}, members) }) } @@ -1906,10 +1959,7 @@ func (suite *GlideTestSuite) TestSinterStore() { res4, err := client.SMembers(key3) assert.NoError(t, err) - assert.Len(t, res4, 1) - for key := range res4 { - assert.Equal(t, key.Value(), "c") - } + assert.Equal(t, map[string]struct{}{"c": {}}, res4) // overwrite existing set, which is also a source set res5, err := client.SInterStore(key2, []string{key1, key2}) @@ -1918,10 +1968,7 @@ func (suite *GlideTestSuite) TestSinterStore() { res6, err := client.SMembers(key2) assert.NoError(t, err) - assert.Len(t, res6, 1) - for key := range res6 { - assert.Equal(t, key.Value(), "c") - } + assert.Equal(t, map[string]struct{}{"c": {}}, res6) // source set is the same as the existing set res7, err := client.SInterStore(key1, []string{key2}) @@ -1930,10 +1977,7 @@ func (suite *GlideTestSuite) TestSinterStore() { res8, err := client.SMembers(key2) assert.NoError(t, err) - assert.Len(t, res8, 1) - for key := range res8 { - assert.Equal(t, key.Value(), "c") - } + assert.Equal(t, map[string]struct{}{"c": {}}, res8) // intersection with non-existing key res9, err := client.SInterStore(key1, []string{key2, nonExistingKey}) @@ -1968,10 +2012,7 @@ func (suite *GlideTestSuite) TestSinterStore() { // check that the key is now empty res13, err := client.SMembers(stringKey) assert.NoError(t, err) - assert.Len(t, res13, 1) - for key := range res13 { - assert.Equal(t, key.Value(), "c") - } + assert.Equal(t, map[string]struct{}{"c": {}}, res13) }) } @@ -2119,17 +2160,17 @@ func (suite *GlideTestSuite) TestSUnion() { nonSetKey := uuid.NewString() memberList1 := []string{"a", "b", "c"} memberList2 := []string{"b", "c", "d", "e"} - expected1 := map[api.Result[string]]struct{}{ - api.CreateStringResult("a"): {}, - api.CreateStringResult("b"): {}, - api.CreateStringResult("c"): {}, - api.CreateStringResult("d"): {}, - api.CreateStringResult("e"): {}, + expected1 := map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, } - expected2 := map[api.Result[string]]struct{}{ - api.CreateStringResult("a"): {}, - api.CreateStringResult("b"): {}, - api.CreateStringResult("c"): {}, + expected2 := map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, } res1, err := client.SAdd(key1, memberList1) @@ -2146,7 +2187,7 @@ func (suite *GlideTestSuite) TestSUnion() { res4, err := client.SUnion([]string{key3}) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), map[api.Result[string]]struct{}{}, res4) + assert.Empty(suite.T(), res4) res5, err := client.SUnion([]string{key1, key3}) assert.Nil(suite.T(), err) @@ -2191,20 +2232,11 @@ func (suite *GlideTestSuite) TestSMove() { res4, err := client.SMembers(key1) assert.NoError(t, err) - expectedSet := map[api.Result[string]]struct{}{ - api.CreateStringResult("2"): {}, - api.CreateStringResult("3"): {}, - } - assert.True(t, reflect.DeepEqual(expectedSet, res4)) + assert.Equal(suite.T(), map[string]struct{}{"2": {}, "3": {}}, res4) res5, err := client.SMembers(key2) assert.NoError(t, err) - expectedSet = map[api.Result[string]]struct{}{ - api.CreateStringResult("1"): {}, - api.CreateStringResult("2"): {}, - api.CreateStringResult("3"): {}, - } - assert.True(t, reflect.DeepEqual(expectedSet, res5)) + assert.Equal(suite.T(), map[string]struct{}{"1": {}, "2": {}, "3": {}}, res5) // moved element already exists in the destination set res6, err := client.SMove(key2, key1, "2") @@ -2213,19 +2245,11 @@ func (suite *GlideTestSuite) TestSMove() { res7, err := client.SMembers(key1) assert.NoError(t, err) - expectedSet = map[api.Result[string]]struct{}{ - api.CreateStringResult("2"): {}, - api.CreateStringResult("3"): {}, - } - assert.True(t, reflect.DeepEqual(expectedSet, res7)) + assert.Equal(suite.T(), map[string]struct{}{"2": {}, "3": {}}, res7) res8, err := client.SMembers(key2) assert.NoError(t, err) - expectedSet = map[api.Result[string]]struct{}{ - api.CreateStringResult("1"): {}, - api.CreateStringResult("3"): {}, - } - assert.True(t, reflect.DeepEqual(expectedSet, res8)) + assert.Equal(suite.T(), map[string]struct{}{"1": {}, "3": {}}, res8) // attempt to move from a non-existing key res9, err := client.SMove(nonExistingKey, key1, "4") @@ -2234,11 +2258,7 @@ func (suite *GlideTestSuite) TestSMove() { res10, err := client.SMembers(key1) assert.NoError(t, err) - expectedSet = map[api.Result[string]]struct{}{ - api.CreateStringResult("2"): {}, - api.CreateStringResult("3"): {}, - } - assert.True(t, reflect.DeepEqual(expectedSet, res10)) + assert.Equal(suite.T(), map[string]struct{}{"2": {}, "3": {}}, res10) // move to a new set res11, err := client.SMove(key1, key3, "2") @@ -2247,13 +2267,11 @@ func (suite *GlideTestSuite) TestSMove() { res12, err := client.SMembers(key1) assert.NoError(t, err) - assert.Len(t, res12, 1) - assert.Contains(t, res12, api.CreateStringResult("3")) + assert.Equal(suite.T(), map[string]struct{}{"3": {}}, res12) res13, err := client.SMembers(key3) assert.NoError(t, err) - assert.Len(t, res13, 1) - assert.Contains(t, res13, api.CreateStringResult("2")) + assert.Equal(suite.T(), map[string]struct{}{"2": {}}, res13) // attempt to move a missing element res14, err := client.SMove(key1, key3, "42") @@ -2262,13 +2280,11 @@ func (suite *GlideTestSuite) TestSMove() { res12, err = client.SMembers(key1) assert.NoError(t, err) - assert.Len(t, res12, 1) - assert.Contains(t, res12, api.CreateStringResult("3")) + assert.Equal(suite.T(), map[string]struct{}{"3": {}}, res12) res13, err = client.SMembers(key3) assert.NoError(t, err) - assert.Len(t, res13, 1) - assert.Contains(t, res13, api.CreateStringResult("2")) + assert.Equal(suite.T(), map[string]struct{}{"2": {}}, res13) // moving missing element to missing key res15, err := client.SMove(key1, nonExistingKey, "42") @@ -2277,8 +2293,7 @@ func (suite *GlideTestSuite) TestSMove() { res12, err = client.SMembers(key1) assert.NoError(t, err) - assert.Len(t, res12, 1) - assert.Contains(t, res12, api.CreateStringResult("3")) + assert.Equal(suite.T(), map[string]struct{}{"3": {}}, res12) // key exists but is not contain a set _, err = client.Set(stringKey, "value") @@ -2774,11 +2789,11 @@ func (suite *GlideTestSuite) TestLMPopAndLMPopCount() { res1, err := client.LMPop([]string{key1}, api.Left) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res1) + assert.Nil(suite.T(), res1) res2, err := client.LMPopCount([]string{key1}, api.Left, int64(1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res2) + assert.Nil(suite.T(), res2) res3, err := client.LPush(key1, []string{"one", "two", "three", "four", "five"}) assert.Nil(suite.T(), err) @@ -2791,7 +2806,7 @@ func (suite *GlideTestSuite) TestLMPopAndLMPopCount() { assert.Nil(suite.T(), err) assert.Equal( suite.T(), - map[api.Result[string]][]api.Result[string]{api.CreateStringResult(key1): {api.CreateStringResult("five")}}, + map[string][]string{key1: {"five"}}, res5, ) @@ -2799,8 +2814,8 @@ func (suite *GlideTestSuite) TestLMPopAndLMPopCount() { assert.Nil(suite.T(), err) assert.Equal( suite.T(), - map[api.Result[string]][]api.Result[string]{ - api.CreateStringResult(key2): {api.CreateStringResult("one"), api.CreateStringResult("two")}, + map[string][]string{ + key2: {"one", "two"}, }, res6, ) @@ -2808,12 +2823,12 @@ func (suite *GlideTestSuite) TestLMPopAndLMPopCount() { suite.verifyOK(client.Set(key3, "value")) res7, err := client.LMPop([]string{key3}, api.Left) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res7) + assert.Nil(suite.T(), res7) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) res8, err := client.LMPop([]string{key3}, "Invalid") - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res8) + assert.Nil(suite.T(), res8) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -2830,11 +2845,11 @@ func (suite *GlideTestSuite) TestBLMPopAndBLMPopCount() { res1, err := client.BLMPop([]string{key1}, api.Left, float64(0.1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res1) + assert.Nil(suite.T(), res1) res2, err := client.BLMPopCount([]string{key1}, api.Left, int64(1), float64(0.1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res2) + assert.Nil(suite.T(), res2) res3, err := client.LPush(key1, []string{"one", "two", "three", "four", "five"}) assert.Nil(suite.T(), err) @@ -2847,7 +2862,7 @@ func (suite *GlideTestSuite) TestBLMPopAndBLMPopCount() { assert.Nil(suite.T(), err) assert.Equal( suite.T(), - map[api.Result[string]][]api.Result[string]{api.CreateStringResult(key1): {api.CreateStringResult("five")}}, + map[string][]string{key1: {"five"}}, res5, ) @@ -2855,8 +2870,8 @@ func (suite *GlideTestSuite) TestBLMPopAndBLMPopCount() { assert.Nil(suite.T(), err) assert.Equal( suite.T(), - map[api.Result[string]][]api.Result[string]{ - api.CreateStringResult(key2): {api.CreateStringResult("one"), api.CreateStringResult("two")}, + map[string][]string{ + key2: {"one", "two"}, }, res6, ) @@ -2864,7 +2879,7 @@ func (suite *GlideTestSuite) TestBLMPopAndBLMPopCount() { suite.verifyOK(client.Set(key3, "value")) res7, err := client.BLMPop([]string{key3}, api.Left, float64(0.1)) - assert.Equal(suite.T(), (map[api.Result[string]][]api.Result[string])(nil), res7) + assert.Nil(suite.T(), res7) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -4482,6 +4497,94 @@ func (suite *GlideTestSuite) TestXRead() { }) } +func (suite *GlideTestSuite) TestXGroupSetId() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + group := uuid.NewString() + consumer := uuid.NewString() + + // Setup: Create stream with 3 entries, create consumer group, read entries to add them to the Pending Entries List + xadd, err := client.XAddWithOptions( + key, + [][]string{{"f0", "v0"}}, + options.NewXAddOptions().SetId("1-0"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "1-0", xadd.Value()) + xadd, err = client.XAddWithOptions( + key, + [][]string{{"f1", "v1"}}, + options.NewXAddOptions().SetId("1-1"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "1-1", xadd.Value()) + xadd, err = client.XAddWithOptions( + key, + [][]string{{"f2", "v2"}}, + options.NewXAddOptions().SetId("1-2"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "1-2", xadd.Value()) + + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key, group, "0"}, + "Can't send XGROUP CREATE as a custom command", + ) + + xreadgroup, err := client.XReadGroup(group, consumer, map[string]string{key: ">"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key: { + "1-0": {{"f0", "v0"}}, + "1-1": {{"f1", "v1"}}, + "1-2": {{"f2", "v2"}}, + }, + }, xreadgroup) + + // Sanity check: xreadgroup should not return more entries since they're all already in the + // Pending Entries List. + xreadgroup, err = client.XReadGroup(group, consumer, map[string]string{key: ">"}) + assert.NoError(suite.T(), err) + assert.Nil(suite.T(), xreadgroup) + + // Reset the last delivered ID for the consumer group to "1-1" + if suite.serverVersion < "7.0.0" { + suite.verifyOK(client.XGroupSetId(key, group, "1-1")) + } else { + opts := options.NewXGroupSetIdOptionsOptions().SetEntriesRead(42) + suite.verifyOK(client.XGroupSetIdWithOptions(key, group, "1-1", opts)) + } + + // xreadgroup should only return entry 1-2 since we reset the last delivered ID to 1-1 + xreadgroup, err = client.XReadGroup(group, consumer, map[string]string{key: ">"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key: { + "1-2": {{"f2", "v2"}}, + }, + }, xreadgroup) + + // An error is raised if XGROUP SETID is called with a non-existing key + _, err = client.XGroupSetId(uuid.NewString(), group, "1-1") + assert.IsType(suite.T(), &api.RequestError{}, err) + + // An error is raised if XGROUP SETID is called with a non-existing group + _, err = client.XGroupSetId(key, uuid.NewString(), "1-1") + assert.IsType(suite.T(), &api.RequestError{}, err) + + // Setting the ID to a non-existing ID is allowed + suite.verifyOK(client.XGroupSetId(key, group, "99-99")) + + // key exists, but is not a stream + key = uuid.NewString() + suite.verifyOK(client.Set(key, "xgroup setid")) + _, err = client.XGroupSetId(key, group, "1-1") + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + func (suite *GlideTestSuite) TestZAddAndZAddIncr() { suite.runWithDefaultClients(func(client api.BaseClient) { key := uuid.New().String() @@ -4662,14 +4765,11 @@ func (suite *GlideTestSuite) TestZPopMin() { res2, err := client.ZPopMin(key1) assert.Nil(suite.T(), err) - assert.Len(suite.T(), res2, 1) - assert.Equal(suite.T(), float64(1.0), res2[api.CreateStringResult("one")].Value()) + assert.Equal(suite.T(), map[string]float64{"one": float64(1)}, res2) res3, err := client.ZPopMinWithCount(key1, 2) assert.Nil(suite.T(), err) - assert.Len(suite.T(), res3, 2) - assert.Equal(suite.T(), float64(2.0), res3[api.CreateStringResult("two")].Value()) - assert.Equal(suite.T(), float64(3.0), res3[api.CreateStringResult("three")].Value()) + assert.Equal(suite.T(), map[string]float64{"two": float64(2), "three": float64(3)}, res3) // non sorted set key _, err = client.Set(key2, "test") @@ -4696,14 +4796,11 @@ func (suite *GlideTestSuite) TestZPopMax() { res2, err := client.ZPopMax(key1) assert.Nil(suite.T(), err) - assert.Len(suite.T(), res2, 1) - assert.Equal(suite.T(), float64(3.0), res2[api.CreateStringResult("three")].Value()) + assert.Equal(suite.T(), map[string]float64{"three": float64(3)}, res2) res3, err := client.ZPopMaxWithCount(key1, 2) assert.Nil(suite.T(), err) - assert.Len(suite.T(), res3, 2) - assert.Equal(suite.T(), float64(2.0), res3[api.CreateStringResult("two")].Value()) - assert.Equal(suite.T(), float64(1.0), res3[api.CreateStringResult("one")].Value()) + assert.Equal(suite.T(), map[string]float64{"two": float64(2), "one": float64(1)}, res3) // non sorted set key _, err = client.Set(key2, "test") @@ -4866,18 +4963,18 @@ func (suite *GlideTestSuite) TestZRangeWithScores() { assert.NoError(t, err) // index [0:1] res, err := client.ZRangeWithScores(key, options.NewRangeByIndexQuery(0, 1)) - expected := map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("a"): api.CreateFloat64Result(1.0), - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + expected := map[string]float64{ + "a": float64(1.0), + "b": float64(2.0), } assert.NoError(t, err) assert.Equal(t, expected, res) // index [0:-1] (all) res, err = client.ZRangeWithScores(key, options.NewRangeByIndexQuery(0, -1)) - expected = map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("a"): api.CreateFloat64Result(1.0), - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), - api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + expected = map[string]float64{ + "a": float64(1.0), + "b": float64(2.0), + "c": float64(3.0), } assert.NoError(t, err) assert.Equal(t, expected, res) @@ -4890,10 +4987,10 @@ func (suite *GlideTestSuite) TestZRangeWithScores() { options.NewInfiniteScoreBoundary(options.NegativeInfinity), options.NewScoreBoundary(3, true)) res, err = client.ZRangeWithScores(key, query) - expected = map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("a"): api.CreateFloat64Result(1.0), - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), - api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + expected = map[string]float64{ + "a": float64(1.0), + "b": float64(2.0), + "c": float64(3.0), } assert.NoError(t, err) assert.Equal(t, expected, res) @@ -4902,9 +4999,9 @@ func (suite *GlideTestSuite) TestZRangeWithScores() { options.NewInfiniteScoreBoundary(options.NegativeInfinity), options.NewScoreBoundary(3, false)) res, err = client.ZRangeWithScores(key, query) - expected = map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("a"): api.CreateFloat64Result(1.0), - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), + expected = map[string]float64{ + "a": float64(1.0), + "b": float64(2.0), } assert.NoError(t, err) assert.Equal(t, expected, res) @@ -4914,9 +5011,9 @@ func (suite *GlideTestSuite) TestZRangeWithScores() { options.NewInfiniteScoreBoundary(options.NegativeInfinity)). SetReverse() res, err = client.ZRangeWithScores(key, query) - expected = map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), - api.CreateStringResult("a"): api.CreateFloat64Result(1.0), + expected = map[string]float64{ + "b": float64(2.0), + "a": float64(1.0), } assert.NoError(t, err) assert.Equal(t, expected, res) @@ -4926,9 +5023,9 @@ func (suite *GlideTestSuite) TestZRangeWithScores() { options.NewInfiniteScoreBoundary(options.PositiveInfinity)). SetLimit(1, 2) res, err = client.ZRangeWithScores(key, query) - expected = map[api.Result[string]]api.Result[float64]{ - api.CreateStringResult("b"): api.CreateFloat64Result(2.0), - api.CreateStringResult("c"): api.CreateFloat64Result(3.0), + expected = map[string]float64{ + "b": float64(2.0), + "c": float64(3.0), } assert.NoError(t, err) assert.Equal(t, expected, res) @@ -5963,36 +6060,42 @@ func (suite *GlideTestSuite) TestXPendingFailures() { }) } -// TODO add XGroupDestroy tests there func (suite *GlideTestSuite) TestXGroupCreate_XGroupDestroy() { suite.runWithDefaultClients(func(client api.BaseClient) { key := uuid.NewString() - group1 := uuid.NewString() - group2 := uuid.NewString() + group := uuid.NewString() id := "0-1" // Stream not created results in error - _, err := client.XGroupCreate(key, group1, id) + _, err := client.XGroupCreate(key, group, id) assert.Error(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) // Stream with option to create creates stream & Group opts := options.NewXGroupCreateOptions().SetMakeStream() - suite.verifyOK(client.XGroupCreateWithOptions(key, group1, id, opts)) + suite.verifyOK(client.XGroupCreateWithOptions(key, group, id, opts)) // ...and again results in BUSYGROUP error, because group names must be unique - _, err = client.XGroupCreate(key, group1, id) + _, err = client.XGroupCreate(key, group, id) assert.ErrorContains(suite.T(), err, "BUSYGROUP") assert.IsType(suite.T(), &api.RequestError{}, err) - // TODO add XGroupDestroy tests there + // Stream Group can be destroyed returns: true + destroyed, err := client.XGroupDestroy(key, group) + assert.NoError(suite.T(), err) + assert.True(suite.T(), destroyed) + + // ...and again results in: false + destroyed, err = client.XGroupDestroy(key, group) + assert.NoError(suite.T(), err) + assert.False(suite.T(), destroyed) // ENTRIESREAD option was added in valkey 7.0.0 opts = options.NewXGroupCreateOptions().SetEntriesRead(100) if suite.serverVersion >= "7.0.0" { - suite.verifyOK(client.XGroupCreateWithOptions(key, group2, id, opts)) + suite.verifyOK(client.XGroupCreateWithOptions(key, group, id, opts)) } else { - _, err = client.XGroupCreateWithOptions(key, group2, id, opts) + _, err = client.XGroupCreateWithOptions(key, group, id, opts) assert.Error(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) } @@ -6000,7 +6103,7 @@ func (suite *GlideTestSuite) TestXGroupCreate_XGroupDestroy() { // key is not a stream key = uuid.NewString() suite.verifyOK(client.Set(key, id)) - _, err = client.XGroupCreate(key, group1, id) + _, err = client.XGroupCreate(key, group, id) assert.Error(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -6296,12 +6399,9 @@ func (suite *GlideTestSuite) TestObjectIdleTime() { "maxmemory-policy": "noeviction", } suite.verifyOK(defaultClient.ConfigSet(keyValueMap)) - key1 := api.CreateStringResult("maxmemory-policy") - value1 := api.CreateStringResult("noeviction") - resultConfigMap := map[api.Result[string]]api.Result[string]{key1: value1} resultConfig, err := defaultClient.ConfigGet([]string{"maxmemory-policy"}) assert.Nil(t, err, "Failed to get configuration") - assert.Equal(t, resultConfigMap, resultConfig, "Configuration mismatch for maxmemory-policy") + assert.Equal(t, keyValueMap, resultConfig, "Configuration mismatch for maxmemory-policy") resultGet, err := defaultClient.Get(key) assert.Nil(t, err) assert.Equal(t, value, resultGet.Value()) @@ -6338,12 +6438,9 @@ func (suite *GlideTestSuite) TestObjectFreq() { "maxmemory-policy": "volatile-lfu", } suite.verifyOK(defaultClient.ConfigSet(keyValueMap)) - key1 := api.CreateStringResult("maxmemory-policy") - value1 := api.CreateStringResult("volatile-lfu") - resultConfigMap := map[api.Result[string]]api.Result[string]{key1: value1} resultConfig, err := defaultClient.ConfigGet([]string{"maxmemory-policy"}) assert.Nil(t, err, "Failed to get configuration") - assert.Equal(t, resultConfigMap, resultConfig, "Configuration mismatch for maxmemory-policy") + assert.Equal(t, keyValueMap, resultConfig, "Configuration mismatch for maxmemory-policy") sleepSec := int64(5) time.Sleep(time.Duration(sleepSec) * time.Second) resultGet, err := defaultClient.Get(key) @@ -6567,28 +6664,29 @@ func (suite *GlideTestSuite) TestXGroupStreamCommands() { assert.NotNil(suite.T(), streamId3) // xack that streamid1 and streamid2 have been processed - command := []string{"XAck", key, groupName, streamId1.Value(), streamId2.Value()} - sendWithCustomCommand(suite, client, command, "Can't send XACK as a custom command") + xackResult, err := client.XAck(key, groupName, []string{streamId1.Value(), streamId2.Value()}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(2), xackResult) // Delete the consumer group and expect 0 pending messages respInt64, err = client.XGroupDelConsumer(key, groupName, consumerName) assert.NoError(suite.T(), err) assert.Equal(suite.T(), int64(0), respInt64) - // TODO: Use XAck when it is added to the Go client // xack streamid_1, and streamid_2 already received returns 0L - command = []string{"XAck", key, groupName, streamId1.Value(), streamId2.Value()} - sendWithCustomCommand(suite, client, command, "Can't send XACK as a custom command") + xackResult, err = client.XAck(key, groupName, []string{streamId1.Value(), streamId2.Value()}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), xackResult) // Consume the last message with the previously deleted consumer (creates the consumer anew) resp, err = client.XReadGroup(groupName, consumerName, map[string]string{key: ">"}) assert.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(resp[key])) - // TODO: Use XAck when it is added to the Go client // Use non existent group, so xack streamid_3 returns 0 - command = []string{"XAck", key, "non-existent-group", streamId3.Value()} - sendWithCustomCommand(suite, client, command, "Can't send XACK as a custom command") + xackResult, err = client.XAck(key, "non-existent-group", []string{streamId3.Value()}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), xackResult) // Delete the consumer group and expect 1 pending message respInt64, err = client.XGroupDelConsumer(key, groupName, consumerName) @@ -6608,6 +6706,193 @@ func (suite *GlideTestSuite) TestXGroupStreamCommands() { }) } +func (suite *GlideTestSuite) TestSetBit_SetSingleBit() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + var resultInt64 int64 + resultInt64, err := client.SetBit(key, 7, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultInt64) + + result, err := client.Get(key) + assert.NoError(suite.T(), err) + assert.Contains(suite.T(), result.Value(), "\x01") + }) +} + +func (suite *GlideTestSuite) TestSetBit_SetAndCheckPreviousBit() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + var resultInt64 int64 + resultInt64, err := client.SetBit(key, 7, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultInt64) + + resultInt64, err = client.SetBit(key, 7, 0) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), resultInt64) + }) +} + +func (suite *GlideTestSuite) TestSetBit_SetMultipleBits() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + var resultInt64 int64 + + resultInt64, err := client.SetBit(key, 3, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultInt64) + + resultInt64, err = client.SetBit(key, 5, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), resultInt64) + + result, err := client.Get(key) + assert.NoError(suite.T(), err) + value := result.Value() + + binaryString := fmt.Sprintf("%08b", value[0]) + + assert.Equal(suite.T(), "00010100", binaryString) + }) +} + +func (suite *GlideTestSuite) TestWait() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.Set(key, "test") + // Test 1: numberOfReplicas (2) + resultInt64, err := client.Wait(2, 2000) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resultInt64 >= 2) + + // Test 2: Invalid timeout (negative) + _, err = client.Wait(2, -1) + + // Assert error and message for invalid timeout + assert.NotNil(suite.T(), err) + }) +} + +func (suite *GlideTestSuite) TestGetBit_ExistingKey_ValidOffset() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + offset := int64(7) + value := int64(1) + + client.SetBit(key, offset, value) + + result, err := client.GetBit(key, offset) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), value, result) + }) +} + +func (suite *GlideTestSuite) TestGetBit_NonExistentKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + offset := int64(10) + + result, err := client.GetBit(key, offset) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), result) + }) +} + +func (suite *GlideTestSuite) TestGetBit_InvalidOffset() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + invalidOffset := int64(-1) + + _, err := client.GetBit(key, invalidOffset) + assert.NotNil(suite.T(), err) + }) +} + +func (suite *GlideTestSuite) TestBitCount_ExistingKey() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + for i := int64(0); i < 8; i++ { + client.SetBit(key, i, 1) + } + + result, err := client.BitCount(key) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(8), result) + }) +} + +func (suite *GlideTestSuite) TestBitCount_ZeroBits() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + + result, err := client.BitCount(key) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), result) + }) +} + +func (suite *GlideTestSuite) TestBitCountWithOptions_StartEnd() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := "TestBitCountWithOptions_StartEnd" + + client.Set(key, value) + + start := int64(1) + end := int64(5) + opts := &options.BitCountOptions{} + opts.SetStart(start) + opts.SetEnd(end) + + result, err := client.BitCountWithOptions(key, opts) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(19), result) + }) +} + +func (suite *GlideTestSuite) TestBitCountWithOptions_StartEndByte() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := "TestBitCountWithOptions_StartEnd" + + client.Set(key, value) + + start := int64(1) + end := int64(5) + opts := &options.BitCountOptions{} + opts.SetStart(start) + opts.SetEnd(end) + opts.SetBitmapIndexType(options.BYTE) + + result, err := client.BitCountWithOptions(key, opts) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(19), result) + }) +} + +func (suite *GlideTestSuite) TestBitCountWithOptions_StartEndBit() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + value := "TestBitCountWithOptions_StartEnd" + + client.Set(key, value) + + start := int64(1) + end := int64(5) + opts := &options.BitCountOptions{} + opts.SetStart(start) + opts.SetEnd(end) + opts.SetBitmapIndexType(options.BIT) + + result, err := client.BitCountWithOptions(key, opts) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(3), result) + }) +} + func (suite *GlideTestSuite) TestCopy() { suite.runWithDefaultClients(func(client api.BaseClient) { key := "testKey1_" + uuid.New().String() diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 3c298f1ee6..2d4a0ec31c 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -180,11 +180,7 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_multipleArgs() { suite.T().Skip("This feature is added in version 7") } configMap := map[string]string{"timeout": "1000", "maxmemory": "1GB"} - key1 := api.CreateStringResult("timeout") - value1 := api.CreateStringResult("1000") - key2 := api.CreateStringResult("maxmemory") - value2 := api.CreateStringResult("1073741824") - resultConfigMap := map[api.Result[string]]api.Result[string]{key1: value1, key2: value2} + resultConfigMap := map[string]string{"timeout": "1000", "maxmemory": "1073741824"} suite.verifyOK(client.ConfigSet(configMap)) result2, err := client.ConfigGet([]string{"timeout", "maxmemory"}) @@ -217,7 +213,7 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_invalidArgs() { assert.IsType(suite.T(), &api.RequestError{}, err) result2, err := client.ConfigGet([]string{"time"}) - assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{}, result2) + assert.Equal(suite.T(), map[string]string{}, result2) assert.Nil(suite.T(), err) }