From 36776dfc6fe3cba0c5ab09c354f974c124170473 Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Thu, 16 Jan 2025 09:21:07 -0800 Subject: [PATCH 01/21] moved docs from interfaces to base client Signed-off-by: Edward Liang --- go/api/base_client.go | 3222 +++++++++++++++++++++- go/api/connection_management_commands.go | 22 +- go/api/generic_base_commands.go | 396 +-- go/api/hash_commands.go | 305 +- go/api/hyperloglog_commands.go | 42 +- go/api/list_commands.go | 613 +--- go/api/set_commands.go | 426 +-- go/api/sorted_set_commands.go | 331 +-- go/api/stream_commands.go | 85 +- go/api/string_commands.go | 395 +-- 10 files changed, 3096 insertions(+), 2741 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 1a67892934..b19fbebb16 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -65,6 +65,9 @@ type baseClient struct { coreClient unsafe.Pointer } +// This is a type which allows for pkgsite to identify functions for documentation. +type Command = baseClient + // Creates a connection by invoking the `create_client` function from Rust library via FFI. // Passes the pointers to callback functions which will be invoked when the command succeeds or fails. // Once the connection is established, this function invokes `free_connection_response` exposed by rust library to free the @@ -179,7 +182,26 @@ func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) { return cStrings, stringLengths } -func (client *baseClient) Set(key string, value string) (string, error) { +// Set the given key with the given value. The return value is a response from Valkey containing the string "OK". +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to store. +// value - The value to store with the given key. +// +// Return value: +// +// `"OK"` response on success. +// +// For example: +// +// result, err := client.Set("key", "value") +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/set/ +func (client *Command) Set(key string, value string) (string, error) { result, err := client.executeCommand(C.Set, []string{key, value}) if err != nil { return defaultStringResponse, err @@ -188,7 +210,39 @@ func (client *baseClient) Set(key string, value string) (string, error) { return handleStringResponse(result) } -func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) { +// SetWithOptions sets the given key with the given value using the given options. The return value is dependent on the +// passed options. If the value is successfully set, "OK" is returned. If value isn't set because of [OnlyIfExists] or +// [OnlyIfDoesNotExist] conditions, api.CreateNilStringResult() is returned. If [SetOptions#ReturnOldValue] is +// set, the old value is returned. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to store. +// value - The value to store with the given key. +// options - The [api.SetOptions]. +// +// Return value: +// +// If the value is successfully set, return api.Result[string] containing "OK". +// If value isn't set because of ConditionalSet.OnlyIfExists or ConditionalSet.OnlyIfDoesNotExist conditions, return +// api.CreateNilStringResult(). +// If SetOptions.returnOldValue is set, return the old value as a String. +// +// For example: +// +// key: initialValue +// result, err := client.SetWithOptions("key", "value", api.NewSetOptionsBuilder() +// .SetExpiry(api.NewExpiryBuilder() +// .SetType(api.Seconds) +// .SetCount(uint64(5) +// )) +// result.Value(): "OK" +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/set/ +func (client *Command) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) { optionArgs, err := options.toArgs() if err != nil { return CreateNilStringResult(), err @@ -202,7 +256,30 @@ func (client *baseClient) SetWithOptions(key string, value string, options *SetO return handleStringOrNilResponse(result) } -func (client *baseClient) Get(key string) (Result[string], error) { +// Get string value associated with the given key, or api.CreateNilStringResult() is returned if no such value +// exists. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to be retrieved from the database. +// +// Return value: +// +// If key exists, returns the value of key as a String. Otherwise, return [api.CreateNilStringResult()]. +// +// For example: +// 1. key: value +// result, err := client.Get("key") +// result.Value(): "value" +// result.IsNil(): false +// 2. result, err := client.Get("nonExistentKey") +// result.Value(): "" +// result.IsNil(): true +// +// [valkey.io]: https://valkey.io/commands/get/ +func (client *Command) Get(key string) (Result[string], error) { result, err := client.executeCommand(C.Get, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -211,7 +288,30 @@ func (client *baseClient) Get(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) GetEx(key string) (Result[string], error) { +// Get string value associated with the given key, or an empty string is returned [api.CreateNilStringResult()] if no such +// value exists. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to be retrieved from the database. +// +// Return value: +// +// If key exists, returns the value of key as a Result[string]. Otherwise, return [api.CreateNilStringResult()]. +// +// For example: +// 1. key: value +// result, err := client.GetEx("key") +// result.Value(): "value" +// result.IsNil(): false +// 2. result, err := client.GetEx("nonExistentKey") +// result.Value(): "" +// result.IsNil(): true +// +// [valkey.io]: https://valkey.io/commands/getex/ +func (client *Command) GetEx(key string) (Result[string], error) { result, err := client.executeCommand(C.GetEx, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -220,7 +320,32 @@ func (client *baseClient) GetEx(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) GetExWithOptions(key string, options *GetExOptions) (Result[string], error) { +// Get string value associated with the given key and optionally sets the expiration of the key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to be retrieved from the database. +// options - The [api.GetExOptions]. +// +// Return value: +// +// If key exists, returns the value of key as a Result[string]. Otherwise, return [api.CreateNilStringResult()]. +// +// For example: +// +// key: initialValue +// result, err := client.GetExWithOptions("key", api.NewGetExOptionsBuilder() +// .SetExpiry(api.NewExpiryBuilder() +// .SetType(api.Seconds) +// .SetCount(uint64(5) +// )) +// result.Value(): "initialValue" +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/getex/ +func (client *Command) GetExWithOptions(key string, options *GetExOptions) (Result[string], error) { optionArgs, err := options.toArgs() if err != nil { return CreateNilStringResult(), err @@ -234,7 +359,32 @@ func (client *baseClient) GetExWithOptions(key string, options *GetExOptions) (R return handleStringOrNilResponse(result) } -func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { +// Sets multiple keys to multiple values in a single operation. +// +// Note: +// +// In cluster mode, if keys in `keyValueMap` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// +// Parameters: +// +// keyValueMap - A key-value map consisting of keys and their respective values to set. +// +// Return value: +// +// `"OK"` on success. +// +// For example: +// +// result, err := client.MSet(map[string]string{"key1": "value1", "key2": "value2"}) +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/mset/ +func (client *Command) MSet(keyValueMap map[string]string) (string, error) { result, err := client.executeCommand(C.MSet, utils.MapToString(keyValueMap)) if err != nil { return defaultStringResponse, err @@ -243,7 +393,37 @@ func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { return handleStringResponse(result) } -func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { +// Sets multiple keys to values if the key does not exist. The operation is atomic, and if one or more keys already exist, +// the entire operation fails. +// +// Note: +// +// In cluster mode, if keys in `keyValueMap` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// +// Parameters: +// +// keyValueMap - A key-value map consisting of keys and their respective values to set. +// +// Return value: +// +// A bool containing true, if all keys were set. false, if no key was set. +// +// For example: +// 1. result, err := client.MSetNX(map[string]string{"key1": "value1", "key2": "value2"}) +// result.Value(): true +// result.IsNil(): false +// 2. key3: initialValue +// result, err := client.MSetNX(map[string]string{"key3": "value3", "key4": "value4"}) +// result.Value(): false +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/msetnx/ +func (client *Command) MSetNX(keyValueMap map[string]string) (bool, error) { result, err := client.executeCommand(C.MSetNX, utils.MapToString(keyValueMap)) if err != nil { return defaultBoolResponse, err @@ -252,7 +432,38 @@ func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { return handleBoolResponse(result) } -func (client *baseClient) MGet(keys []string) ([]Result[string], error) { +// Retrieves the values of multiple keys. +// +// Note: +// +// In cluster mode, if keys in `keys` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// +// Parameters: +// +// keys - A list of keys to retrieve values for. +// +// Return value: +// +// An array of values corresponding to the provided keys. +// If a key is not found, its corresponding value in the list will be a [api.CreateNilStringResult()] +// +// For example: +// +// key1: value1, key2: value2 +// result, err := client.MGet([]string{"key1", "key2", "key3"}) +// result : { +// api.CreateStringResult("value1), +// api.CreateStringResult("value2"), +// api.CreateNilStringResult() +// } +// +// [valkey.io]: https://valkey.io/commands/mget/ +func (client *Command) MGet(keys []string) ([]Result[string], error) { result, err := client.executeCommand(C.MGet, keys) if err != nil { return nil, err @@ -261,7 +472,26 @@ func (client *baseClient) MGet(keys []string) ([]Result[string], error) { return handleStringArrayResponse(result) } -func (client *baseClient) Incr(key string) (int64, error) { +// Increments the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to increment its value. +// +// Return value: +// +// The value of `key` after the increment. +// +// For example: +// +// key: 1 +// result, err := client.Incr("key"); +// result: 2 +// +// [valkey.io]: https://valkey.io/commands/incr/ +func (client *Command) Incr(key string) (int64, error) { result, err := client.executeCommand(C.Incr, []string{key}) if err != nil { return defaultIntResponse, err @@ -270,7 +500,27 @@ func (client *baseClient) Incr(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { +// Increments the number stored at key by amount. If key does not exist, it is set to 0 before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to increment its value. +// amount - The amount to increment. +// +// Return value: +// +// The value of `key` after the increment. +// +// For example: +// +// key: 1 +// result, err := client.IncrBy("key", 2) +// result: 3 +// +// [valkey.io]: https://valkey.io/commands/incrby/ +func (client *Command) IncrBy(key string, amount int64) (int64, error) { result, err := client.executeCommand(C.IncrBy, []string{key, utils.IntToString(amount)}) if err != nil { return defaultIntResponse, err @@ -279,7 +529,29 @@ func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) IncrByFloat(key string, amount float64) (float64, error) { +// Increments the string representing a floating point number stored at key by amount. By using a negative increment value, +// the result is that the value stored at key is decremented. If key does not exist, it is set to `0` before performing the +// operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to increment its value. +// amount - The amount to increment. +// +// Return value: +// +// The value of key after the increment. +// +// For example: +// +// key: 1 +// result, err := client.IncrBy("key", 0.5) +// result: 1.5 +// +// [valkey.io]: https://valkey.io/commands/incrbyfloat/ +func (client *Command) IncrByFloat(key string, amount float64) (float64, error) { result, err := client.executeCommand( C.IncrByFloat, []string{key, utils.FloatToString(amount)}, @@ -291,7 +563,26 @@ func (client *baseClient) IncrByFloat(key string, amount float64) (float64, erro return handleFloatResponse(result) } -func (client *baseClient) Decr(key string) (int64, error) { +// Decrements the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to decrement its value. +// +// Return value: +// +// The value of `key` after the decrement. +// +// For example: +// +// key: 1 +// result, err := client.Decr("key") +// result: 0 +// +// [valkey.io]: https://valkey.io/commands/decr/ +func (client *Command) Decr(key string) (int64, error) { result, err := client.executeCommand(C.Decr, []string{key}) if err != nil { return defaultIntResponse, err @@ -300,7 +591,27 @@ func (client *baseClient) Decr(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { +// Decrements the number stored at code by amount. If key does not exist, it is set to 0 before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to decrement its value. +// amount - The amount to decrement. +// +// Return value: +// +// The value of `key` after the decrement. +// +// For example: +// +// key: 1 +// result, err := client.DecrBy("key", 2) +// result: -1 +// +// [valkey.io]: https://valkey.io/commands/decrby/ +func (client *Command) DecrBy(key string, amount int64) (int64, error) { result, err := client.executeCommand(C.DecrBy, []string{key, utils.IntToString(amount)}) if err != nil { return defaultIntResponse, err @@ -309,7 +620,27 @@ func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) Strlen(key string) (int64, error) { +// Returns the length of the string value stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key to check its length. +// +// Return value: +// +// The length of the string value stored at `key`. +// If key does not exist, it is treated as an empty string, and the command returns `0`. +// +// For example: +// +// key: value +// result, err := client.Strlen("key") +// result: 5 +// +// [valkey.io]: https://valkey.io/commands/strlen/ +func (client *Command) Strlen(key string) (int64, error) { result, err := client.executeCommand(C.Strlen, []string{key}) if err != nil { return defaultIntResponse, err @@ -318,7 +649,34 @@ func (client *baseClient) Strlen(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { +// Overwrites part of the string stored at key, starting at the specified byte's offset, for the entire length of value. +// If the offset is larger than the current length of the string at key, the string is padded with zero bytes to make +// offset fit. +// Creates the key if it doesn't exist. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the string to update. +// offset - The position in the string where value should be written. +// value - The string written with offset. +// +// Return value: +// +// The length of the string stored at `key` after it was modified. +// +// For example: +// 1. result, err := client.SetRange("key", 6, "GLIDE"); +// result: 11 (New key created with length of 11 bytes) +// value, err := client.Get("key") +// value.Value(): "\x00\x00\x00\x00\x00\x00GLIDE" +// 2. "key": "愛" (value char takes 3 bytes) +// result, err = client.SetRange("key", 1, "a") +// result.Value(): �a� // (becomes an invalid UTF-8 string) +// +// [valkey.io]: https://valkey.io/commands/setrange/ +func (client *Command) SetRange(key string, offset int, value string) (int64, error) { result, err := client.executeCommand(C.SetRange, []string{key, strconv.Itoa(offset), value}) if err != nil { return defaultIntResponse, err @@ -327,7 +685,35 @@ func (client *baseClient) SetRange(key string, offset int, value string) (int64, return handleIntResponse(result) } -func (client *baseClient) GetRange(key string, start int, end int) (string, error) { +// Returns the substring of the string value stored at key, determined by the byte's offsets start and end (both are +// inclusive). +// Negative offsets can be used in order to provide an offset starting from the end of the string. So `-1` means the last +// character, `-2` the penultimate and so forth. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the string. +// start - The starting offset. +// end - The ending offset. +// +// Return value: +// +// A substring extracted from the value stored at key. Returns empty string if the offset is out of bounds. +// +// For example: +// 1. mykey: "This is a string" +// result, err := client.GetRange("mykey", 0, 3) +// result: "This" +// result, err := client.GetRange("mykey", -3, -1) +// result: "ing" (extracted last 3 characters of a string) +// 2. "key": "愛" (value char takes 3 bytes) +// result, err = client.GetRange("key", 0, 1) +// result: "�" (returns an invalid UTF-8 string) +// +// [valkey.io]: https://valkey.io/commands/getrange/ +func (client *Command) GetRange(key string, start int, end int) (string, error) { result, err := client.executeCommand(C.GetRange, []string{key, strconv.Itoa(start), strconv.Itoa(end)}) if err != nil { return defaultStringResponse, err @@ -336,7 +722,27 @@ func (client *baseClient) GetRange(key string, start int, end int) (string, erro return handleStringResponse(result) } -func (client *baseClient) Append(key string, value string) (int64, error) { +// Appends a value to a key. If key does not exist it is created and set as an empty string, so APPEND will be similar to +// SET in this special case. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the string. +// value - The value to append. +// +// Return value: +// +// The length of the string after appending the value. +// +// For example: +// +// result, err := client.Append("key", "value") +// result: 5 +// +// [valkey.io]: https://valkey.io/commands/append/ +func (client *Command) Append(key string, value string) (int64, error) { result, err := client.executeCommand(C.Append, []string{key, value}) if err != nil { return defaultIntResponse, err @@ -345,7 +751,39 @@ func (client *baseClient) Append(key string, value string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) LCS(key1 string, key2 string) (string, error) { +// Returns the longest common subsequence between strings stored at key1 and key2. +// +// Since: +// +// Valkey 7.0 and above. +// +// Note: +// +// In cluster mode, if keys in `keyValueMap` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// +// Parameters: +// +// key1 - The key that stores the first string. +// key2 - The key that stores the second string. +// +// Return value: +// +// The longest common subsequence between the 2 strings. +// An empty string is returned if the keys do not exist or have no common subsequences. +// +// For example: +// +// testKey1: foo, testKey2: fao +// result, err := client.LCS("testKey1", "testKey2") +// result: "fo" +// +// [valkey.io]: https://valkey.io/commands/lcs/ +func (client *Command) LCS(key1 string, key2 string) (string, error) { result, err := client.executeCommand(C.LCS, []string{key1, key2}) if err != nil { return defaultStringResponse, err @@ -354,7 +792,23 @@ func (client *baseClient) LCS(key1 string, key2 string) (string, error) { return handleStringResponse(result) } -func (client *baseClient) GetDel(key string) (Result[string], error) { +// GetDel gets the value associated with the given key and deletes the key. +// +// Parameters: +// +// key - The key to get and delete. +// +// Return value: +// +// If key exists, returns the value of the key as a String and deletes the key. +// If key does not exist, returns a [api.NilResult[string]] (api.CreateNilStringResult()). +// +// For example: +// +// result, err := client.GetDel("key") +// +// [valkey.io]: https://valkey.io/commands/getdel/ +func (client *Command) GetDel(key string) (Result[string], error) { if key == "" { return CreateNilStringResult(), errors.New("key is required") } @@ -367,7 +821,31 @@ func (client *baseClient) GetDel(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) HGet(key string, field string) (Result[string], error) { +// HGet returns the value associated with field in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field in the hash stored at key to retrieve from the database. +// +// Return value: +// The Result[string] associated with field, or [api.NilResult[string]](api.CreateNilStringResult()) when field is not +// present in the hash or key does not exist. +// +// For example: +// +// Assume we have the following hash: +// my_hash := map[string]string{"field1": "value", "field2": "another_value"} +// payload, err := client.HGet("my_hash", "field1") +// // payload.Value(): "value" +// // payload.IsNil(): false +// payload, err = client.HGet("my_hash", "nonexistent_field") +// // payload equals api.CreateNilStringResult() +// +// [valkey.io]: https://valkey.io/commands/hget/ +func (client *Command) HGet(key string, field string) (Result[string], error) { result, err := client.executeCommand(C.HGet, []string{key, field}) if err != nil { return CreateNilStringResult(), err @@ -376,7 +854,29 @@ 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) { +// HGetAll returns all fields and values of the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// +// Return value: +// +// A map of all fields and their values as Result[string] in the hash, or an empty map when key does not exist. +// +// 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} +// +// [valkey.io]: https://valkey.io/commands/hgetall/ +func (client *Command) HGetAll(key string) (map[Result[string]]Result[string], error) { result, err := client.executeCommand(C.HGetAll, []string{key}) if err != nil { return nil, err @@ -385,16 +885,63 @@ func (client *baseClient) HGetAll(key string) (map[Result[string]]Result[string] return handleStringToStringMapResponse(result) } -func (client *baseClient) HMGet(key string, fields []string) ([]Result[string], error) { - result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) - if err != nil { - return nil, err - } - +// HMGet returns the values associated with the specified fields in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// fields - The fields in the hash stored at key to retrieve from the database. +// +// Return value: +// +// An array of Result[string]s associated with the given fields, in the same order as they are requested. +// +// For every field that does not exist in the hash, a [api.NilResult[string]](api.CreateNilStringResult()) is +// returned. +// +// If key does not exist, returns an empty string array. +// +// For example: +// +// values, err := client.HMGet("my_hash", []string{"field1", "field2"}) +// // value1 equals api.CreateStringResult("value1") +// // value2 equals api.CreateStringResult("value2") +// // values equals []api.Result[string]{value1, value2} +// +// [valkey.io]: https://valkey.io/commands/hmget/ +func (client *Command) HMGet(key string, fields []string) ([]Result[string], error) { + result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result) } -func (client *baseClient) HSet(key string, values map[string]string) (int64, error) { +// HSet sets the specified fields to their respective values in the hash stored at key. +// This command overwrites the values of specified fields that exist in the hash. +// If key doesn't exist, a new key holding a hash is created. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// values - A map of field-value pairs to set in the hash. +// +// Return value: +// +// The number of fields that were added or updated. +// +// For example: +// +// num, err := client.HSet("my_hash", map[string]string{"field": "value", "field2": "value2"}) +// // num: 2 +// +// [valkey.io]: https://valkey.io/commands/hset/ +func (client *Command) HSet(key string, values map[string]string) (int64, error) { result, err := client.executeCommand(C.HSet, utils.ConvertMapToKeyValueStringArray(key, values)) if err != nil { return defaultIntResponse, err @@ -403,7 +950,34 @@ func (client *baseClient) HSet(key string, values map[string]string) (int64, err return handleIntResponse(result) } -func (client *baseClient) HSetNX(key string, field string, value string) (bool, error) { +// HSetNX sets field in the hash stored at key to value, only if field does not yet exist. +// If key does not exist, a new key holding a hash is created. +// If field already exists, this operation has no effect. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field to set. +// value - The value to set. +// +// Return value: +// +// A bool containing true if field is a new field in the hash and value was set. +// false if field already exists in the hash and no operation was performed. +// +// For example: +// +// payload1, err := client.HSetNX("myHash", "field", "value") +// // payload1.Value(): true +// // payload1.IsNil(): false +// payload2, err := client.HSetNX("myHash", "field", "newValue") +// // payload2.Value(): false +// // payload2.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/hsetnx/ +func (client *Command) HSetNX(key string, field string, value string) (bool, error) { result, err := client.executeCommand(C.HSetNX, []string{key, field, value}) if err != nil { return defaultBoolResponse, err @@ -412,7 +986,27 @@ func (client *baseClient) HSetNX(key string, field string, value string) (bool, return handleBoolResponse(result) } -func (client *baseClient) HDel(key string, fields []string) (int64, error) { +// HDel removes the specified fields from the hash stored at key. +// Specified fields that do not exist within this hash are ignored. +// If key does not exist, it is treated as an empty hash and this command returns 0. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// fields - The fields to remove from the hash stored at key. +// +// Return value: +// The number of fields that were removed from the hash, not including specified but non-existing fields. +// +// For example: +// +// num, err := client.HDel("my_hash", []string{"field_1", "field_2"}) +// // num: 2 +// +// [valkey.io]: https://valkey.io/commands/hdel/ +func (client *Command) HDel(key string, fields []string) (int64, error) { result, err := client.executeCommand(C.HDel, append([]string{key}, fields...)) if err != nil { return defaultIntResponse, err @@ -421,7 +1015,28 @@ func (client *baseClient) HDel(key string, fields []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) HLen(key string) (int64, error) { +// HLen returns the number of fields contained in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// +// Return value: +// +// The number of fields in the hash, or `0` when key does not exist. +// If key holds a value that is not a hash, an error is returned. +// +// For example: +// +// num1, err := client.HLen("myHash") +// // num: 3 +// num2, err := client.HLen("nonExistingKey") +// // num: 0 +// +// [valkey.io]: https://valkey.io/commands/hlen/ +func (client *Command) HLen(key string) (int64, error) { result, err := client.executeCommand(C.HLen, []string{key}) if err != nil { return defaultIntResponse, err @@ -430,7 +1045,28 @@ func (client *baseClient) HLen(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) HVals(key string) ([]Result[string], error) { +// HVals returns all values in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// +// Return value: +// +// A slice of Result[string]s containing all the values in the hash, or an empty slice when key does not exist. +// +// For example: +// +// values, err := client.HVals("myHash") +// // value1 equals api.CreateStringResult("value1") +// // value2 equals api.CreateStringResult("value2") +// // value3 equals api.CreateStringResult("value3") +// // values equals []api.Result[string]{value1, value2, value3} +// +// [valkey.io]: https://valkey.io/commands/hvals/ +func (client *Command) HVals(key string) ([]Result[string], error) { result, err := client.executeCommand(C.HVals, []string{key}) if err != nil { return nil, err @@ -439,7 +1075,31 @@ func (client *baseClient) HVals(key string) ([]Result[string], error) { return handleStringArrayResponse(result) } -func (client *baseClient) HExists(key string, field string) (bool, error) { +// HExists returns if field is an existing field in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field to check in the hash stored at key. +// +// Return value: +// +// A bool containing true if the hash contains the specified field. +// false if the hash does not contain the field, or if the key does not exist. +// +// For example: +// +// exists, err := client.HExists("my_hash", "field1") +// // exists.Value(): true +// // exists.IsNil(): false +// exists, err = client.HExists("my_hash", "non_existent_field") +// // exists.Value(): false +// // exists.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/hexists/ +func (client *Command) HExists(key string, field string) (bool, error) { result, err := client.executeCommand(C.HExists, []string{key, field}) if err != nil { return defaultBoolResponse, err @@ -448,7 +1108,27 @@ func (client *baseClient) HExists(key string, field string) (bool, error) { return handleBoolResponse(result) } -func (client *baseClient) HKeys(key string) ([]Result[string], error) { +// HKeys returns all field names in the hash stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// +// Return value: +// +// A slice of Result[string]s containing all the field names in the hash, or an empty slice when key does not exist. +// +// For example: +// +// names, err := client.HKeys("my_hash") +// // field1 equals api.CreateStringResult("field_1") +// // field2 equals api.CreateStringResult("field_2") +// // names equals []api.Result[string]{field1, field2} +// +// [valkey.io]: https://valkey.io/commands/hkeys/ +func (client *Command) HKeys(key string) ([]Result[string], error) { result, err := client.executeCommand(C.HKeys, []string{key}) if err != nil { return nil, err @@ -457,7 +1137,28 @@ func (client *baseClient) HKeys(key string) ([]Result[string], error) { return handleStringArrayResponse(result) } -func (client *baseClient) HStrLen(key string, field string) (int64, error) { +// HStrLen returns the string length of the value associated with field in the hash stored at key. +// If the key or the field do not exist, 0 is returned. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field to get the string length of its value. +// +// Return value: +// +// The length of the string value associated with field, or `0` when field or key do not exist. +// +// For example: +// +// strlen, err := client.HStrLen("my_hash", "my_field") +// // strlen.Value(): 10 +// // strlen.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/hstrlen/ +func (client *Command) HStrLen(key string, field string) (int64, error) { result, err := client.executeCommand(C.HStrlen, []string{key, field}) if err != nil { return defaultIntResponse, err @@ -466,7 +1167,30 @@ func (client *baseClient) HStrLen(key string, field string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) HIncrBy(key string, field string, increment int64) (int64, error) { +// Increments the number stored at `field` in the hash stored at `key` by increment. +// By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. +// If `field` or `key` does not exist, it is set to 0 before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field in the hash stored at `key` to increment its value. +// increment - The amount to increment. +// +// Return value: +// +// The value of `field` in the hash stored at `key` after the increment. +// +// Example: +// +// _, err := client.HSet("key", map[string]string{"field": "10"}) +// hincrByResult, err := client.HIncrBy("key", "field", 1) +// // hincrByResult: 11 +// +// [valkey.io]: https://valkey.io/commands/hincrby/ +func (client *Command) HIncrBy(key string, field string, increment int64) (int64, error) { result, err := client.executeCommand(C.HIncrBy, []string{key, field, utils.IntToString(increment)}) if err != nil { return defaultIntResponse, err @@ -475,7 +1199,30 @@ func (client *baseClient) HIncrBy(key string, field string, increment int64) (in return handleIntResponse(result) } -func (client *baseClient) HIncrByFloat(key string, field string, increment float64) (float64, error) { +// Increments the string representing a floating point number stored at `field` in the hash stored at `key` by increment. +// By using a negative increment value, the value stored at `field` in the hash stored at `key` is decremented. +// If `field` or `key` does not exist, it is set to `0` before performing the operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// field - The field in the hash stored at `key` to increment its value. +// increment - The amount to increment. +// +// Return value: +// +// The value of `field` in the hash stored at `key` after the increment. +// +// Example: +// +// _, err := client.HSet("key", map[string]string{"field": "10"}) +// hincrByFloatResult, err := client.HIncrByFloat("key", "field", 1.5) +// // hincrByFloatResult: 11.5 +// +// [valkey.io]: https://valkey.io/commands/hincrbyfloat/ +func (client *Command) HIncrByFloat(key string, field string, increment float64) (float64, error) { result, err := client.executeCommand(C.HIncrByFloat, []string{key, field, utils.FloatToString(increment)}) if err != nil { return defaultFloatResponse, err @@ -484,7 +1231,33 @@ func (client *baseClient) HIncrByFloat(key string, field string, increment float return handleFloatResponse(result) } -func (client *baseClient) HScan(key string, cursor string) (Result[string], []Result[string], error) { +// Iterates fields of Hash types and their associated values. This definition of HSCAN command does not include the +// optional arguments of the command. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// cursor - The cursor that points to the next iteration of results. A value of "0" indicates the start of the search. +// +// Return value: +// +// An array of the cursor and the subset of the hash held by `key`. The first element is always the `cursor` +// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the subset. +// The second element is always an array of the subset of the set held in `key`. The array in the +// second element is always a flattened series of String pairs, where the key is at even indices +// and the value is at odd indices. +// +// Example: +// +// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} +// resCursor, resCollection, err = client.HScan(key, initialCursor) +// // resCursor = {0 false} +// // resCollection = [{a false} {1 false} {b false} {2 false}] +// +// [valkey.io]: https://valkey.io/commands/hscan/ +func (client *Command) HScan(key string, cursor string) (Result[string], []Result[string], error) { result, err := client.executeCommand(C.HScan, []string{key, cursor}) if err != nil { return CreateNilStringResult(), nil, err @@ -492,7 +1265,37 @@ func (client *baseClient) HScan(key string, cursor string) (Result[string], []Re return handleScanResponse(result) } -func (client *baseClient) HScanWithOptions( +// Iterates fields of Hash types and their associated values. This definition of HSCAN includes optional arguments of the +// command. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the hash. +// cursor - The cursor that points to the next iteration of results. A value of "0" indicates the start of the search. +// options - The [api.HashScanOptions]. +// +// Return value: +// +// An array of the cursor and the subset of the hash held by `key`. The first element is always the `cursor` +// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the subset. +// The second element is always an array of the subset of the set held in `key`. The array in the +// second element is always a flattened series of String pairs, where the key is at even indices +// and the value is at odd indices. +// +// Example: +// +// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} +// opts := options.NewHashScanOptionsBuilder().SetMatch("a") +// resCursor, resCollection, err = client.HScan(key, initialCursor, opts) +// // resCursor = {0 false} +// // resCollection = [{a false} {1 false}] +// // The resCollection only contains the hash map entry that matches with the match option provided with the command +// // input. +// +// [valkey.io]: https://valkey.io/commands/hscan/ +func (client *Command) HScanWithOptions( key string, cursor string, options *options.HashScanOptions, @@ -509,7 +1312,28 @@ func (client *baseClient) HScanWithOptions( return handleScanResponse(result) } -func (client *baseClient) LPush(key string, elements []string) (int64, error) { +// Inserts all the specified values at the head of the list stored at key. elements are inserted one after the other to the +// head of the list, from the leftmost element to the rightmost element. If key does not exist, it is created as an empty +// list before performing the push operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// elements - The elements to insert at the head of the list stored at key. +// +// Return value: +// +// The length of the list after the push operation. +// +// For example: +// +// result, err := client.LPush("my_list", []string{"value1", "value2"}) +// result: 2 +// +// [valkey.io]: https://valkey.io/commands/lpush/ +func (client *Command) LPush(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.LPush, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -518,7 +1342,31 @@ func (client *baseClient) LPush(key string, elements []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) LPop(key string) (Result[string], error) { +// Removes and returns the first elements of the list stored at key. The command pops a single element from the beginning +// of the list. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// +// Return value: +// +// The Result[string] containing the value of the first element. +// If key does not exist, [api.CreateNilStringResult()] will be returned. +// +// For example: +// 1. result, err := client.LPush("my_list", []string{"value1", "value2"}) +// value, err := client.LPop("my_list") +// value.Value(): "value2" +// result.IsNil(): false +// 2. result, err := client.LPop("non_existent") +// result.Value(): "" +// result.IsNil(); true +// +// [valkey.io]: https://valkey.io/commands/lpop/ +func (client *Command) LPop(key string) (Result[string], error) { result, err := client.executeCommand(C.LPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -527,7 +1375,28 @@ func (client *baseClient) LPop(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) LPopCount(key string, count int64) ([]Result[string], error) { +// Removes and returns up to count elements of the list stored at key, depending on the list's length. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// count - The count of the elements to pop from the list. +// +// Return value: +// +// An array of the popped elements as Result[string] will be returned depending on the list's length +// If key does not exist, nil will be returned. +// +// For example: +// 1. result, err := client.LPopCount("my_list", 2) +// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")} +// 2. result, err := client.LPopCount("non_existent") +// result: nil +// +// [valkey.io]: https://valkey.io/commands/lpop/ +func (client *Command) LPopCount(key string, count int64) ([]Result[string], error) { result, err := client.executeCommand(C.LPop, []string{key, utils.IntToString(count)}) if err != nil { return nil, err @@ -536,7 +1405,29 @@ func (client *baseClient) LPopCount(key string, count int64) ([]Result[string], return handleStringArrayOrNullResponse(result) } -func (client *baseClient) LPos(key string, element string) (Result[int64], error) { +// Returns the index of the first occurrence of element inside the list specified by key. If no match is found, +// [api.CreateNilInt64Result()] is returned. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The name of the list. +// element - The value to search for within the list. +// +// Return value: +// The Result[int64] containing the index of the first occurrence of element, or [api.CreateNilInt64Result()] if element is +// not in the list. +// +// For example: +// +// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"}) +// position, err := client.LPos("my_list", "e") +// position.Value(): 4 +// position.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/lpos/ +func (client *Command) LPos(key string, element string) (Result[int64], error) { result, err := client.executeCommand(C.LPos, []string{key, element}) if err != nil { return CreateNilInt64Result(), err @@ -545,7 +1436,31 @@ func (client *baseClient) LPos(key string, element string) (Result[int64], error return handleIntOrNilResponse(result) } -func (client *baseClient) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) { +// Returns the index of an occurrence of element within a list based on the given options. If no match is found, +// [api.CreateNilInt64Result()] is returned. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The name of the list. +// element - The value to search for within the list. +// options - The LPos options. +// +// Return value: +// +// The Result[int64] containing the index of element, or [api.CreateNilInt64Result()] if element is not in the list. +// +// For example: +// 1. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"}) +// result, err := client.LPosWithOptions("my_list", "e", api.NewLPosOptionsBuilder().SetRank(2)) +// result.Value(): 5 (Returns the second occurrence of the element "e") +// 2. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e"}) +// result, err := client.LPosWithOptions("my_list", "e", api.NewLPosOptionsBuilder().SetRank(1).SetMaxLen(1000)) +// result.Value(): 4 +// +// [valkey.io]: https://valkey.io/commands/lpos/ +func (client *Command) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) { result, err := client.executeCommand(C.LPos, append([]string{key, element}, options.toArgs()...)) if err != nil { return CreateNilInt64Result(), err @@ -554,7 +1469,28 @@ func (client *baseClient) LPosWithOptions(key string, element string, options *L return handleIntOrNilResponse(result) } -func (client *baseClient) LPosCount(key string, element string, count int64) ([]Result[int64], error) { +// Returns an array of indices of matching elements within a list. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The name of the list. +// element - The value to search for within the list. +// count - The number of matches wanted. +// +// Return value: +// +// An array that holds the indices of the matching elements within the list. +// +// For example: +// +// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// result, err := client.LPosCount("my_list", "e", int64(3)) +// result: []api.Result[int64]{api.CreateInt64Result(4), api.CreateInt64Result(5), api.CreateInt64Result(6)} +// +// [valkey.io]: https://valkey.io/commands/lpos/ +func (client *Command) LPosCount(key string, element string, count int64) ([]Result[int64], error) { result, err := client.executeCommand(C.LPos, []string{key, element, CountKeyword, utils.IntToString(count)}) if err != nil { return nil, err @@ -563,7 +1499,37 @@ func (client *baseClient) LPosCount(key string, element string, count int64) ([] return handleIntArrayResponse(result) } -func (client *baseClient) LPosCountWithOptions( +// Returns an array of indices of matching elements within a list based on the given options. If no match is found, an +// empty array is returned. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The name of the list. +// element - The value to search for within the list. +// count - The number of matches wanted. +// options - The LPos options. +// +// Return value: +// +// An array that holds the indices of the matching elements within the list. +// +// For example: +// 1. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// result, err := client.LPosWithOptions("my_list", "e", int64(1), api.NewLPosOptionsBuilder().SetRank(2)) +// result: []api.Result[int64]{api.CreateInt64Result(5)} +// 2. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// result, err := client.LPosWithOptions( +// "my_list", +// "e", +// int64(3), +// api.NewLPosOptionsBuilder().SetRank(2).SetMaxLen(1000), +// ) +// result: []api.Result[int64]{api.CreateInt64Result(5), api.CreateInt64Result(6)} +// +// [valkey.io]: https://valkey.io/commands/lpos/ +func (client *Command) LPosCountWithOptions( key string, element string, count int64, @@ -580,7 +1546,28 @@ func (client *baseClient) LPosCountWithOptions( return handleIntArrayResponse(result) } -func (client *baseClient) RPush(key string, elements []string) (int64, error) { +// Inserts all the specified values at the tail of the list stored at key. +// elements are inserted one after the other to the tail of the list, from the leftmost element to the rightmost element. +// If key does not exist, it is created as an empty list before performing the push operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// elements - The elements to insert at the tail of the list stored at key. +// +// Return value: +// +// The length of the list after the push operation. +// +// For example: +// +// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// result: 7 +// +// [valkey.io]: https://valkey.io/commands/rpush/ +func (client *Command) RPush(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.RPush, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -589,7 +1576,26 @@ func (client *baseClient) RPush(key string, elements []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SAdd(key string, members []string) (int64, error) { +// SAdd adds specified members to the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key where members will be added to its set. +// members - A list of members to add to the set stored at key. +// +// Return value: +// +// The number of members that were added to the set, excluding members already present. +// +// For example: +// +// result, err := client.SAdd("my_set", []string{"member1", "member2"}) +// // result: 2 +// +// [valkey.io]: https://valkey.io/commands/sadd/ +func (client *Command) SAdd(key string, members []string) (int64, error) { result, err := client.executeCommand(C.SAdd, append([]string{key}, members...)) if err != nil { return defaultIntResponse, err @@ -598,7 +1604,26 @@ func (client *baseClient) SAdd(key string, members []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SRem(key string, members []string) (int64, error) { +// SRem removes specified members from the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key from which members will be removed. +// members - A list of members to remove from the set stored at key. +// +// Return value: +// +// The number of members that were removed from the set, excluding non-existing members. +// +// For example: +// +// result, err := client.SRem("my_set", []string{"member1", "member2"}) +// // result: 2 +// +// [valkey.io]: https://valkey.io/commands/srem/ +func (client *Command) SRem(key string, members []string) (int64, error) { result, err := client.executeCommand(C.SRem, append([]string{key}, members...)) if err != nil { return defaultIntResponse, err @@ -607,7 +1632,31 @@ func (client *baseClient) SRem(key string, members []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SUnionStore(destination string, keys []string) (int64, error) { +// SUnionStore stores the members of the union of all given sets specified by `keys` into a new set at `destination`. +// +// Note: When in cluster mode, `destination` and all `keys` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// destination - The key of the destination set. +// keys - The keys from which to retrieve the set members. +// +// Return value: +// +// The number of elements in the resulting set. +// +// Example: +// +// result, err := client.SUnionStore("my_set", []string{"set1", "set2"}) +// if err != nil { +// fmt.Println(result) +// } +// // Output: 2 - Two elements were stored at "my_set", and those elements are the union of "set1" and "set2". +// +// [valkey.io]: https://valkey.io/commands/sunionstore/ +func (client *Command) SUnionStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SUnionStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -616,7 +1665,31 @@ func (client *baseClient) SUnionStore(destination string, keys []string) (int64, return handleIntResponse(result) } -func (client *baseClient) SMembers(key string) (map[Result[string]]struct{}, error) { +// SMembers retrieves all the members of the set value stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// 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. +// +// 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"): {} +// // } +// +// [valkey.io]: https://valkey.io/commands/smembers/ +func (client *Command) SMembers(key string) (map[Result[string]]struct{}, error) { result, err := client.executeCommand(C.SMembers, []string{key}) if err != nil { return nil, err @@ -625,7 +1698,25 @@ func (client *baseClient) SMembers(key string) (map[Result[string]]struct{}, err return handleStringSetResponse(result) } -func (client *baseClient) SCard(key string) (int64, error) { +// SCard retrieves the set cardinality (number of elements) of the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key from which to retrieve the number of set members. +// +// Return value: +// +// The cardinality (number of elements) of the set, or `0` if the key does not exist. +// +// Example: +// +// result, err := client.SCard("my_set") +// // result: 3 +// +// [valkey.io]: https://valkey.io/commands/scard/ +func (client *Command) SCard(key string) (int64, error) { result, err := client.executeCommand(C.SCard, []string{key}) if err != nil { return defaultIntResponse, err @@ -634,7 +1725,31 @@ func (client *baseClient) SCard(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SIsMember(key string, member string) (bool, error) { +// SIsMember returns if member is a member of the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the set. +// member - The member to check for existence in the set. +// +// Return value: +// +// A bool containing true if the member exists in the set, false otherwise. +// If key doesn't exist, it is treated as an empty set and the method returns false. +// +// Example: +// +// result1, err := client.SIsMember("mySet", "member1") +// // result1.Value(): true +// // Indicates that "member1" exists in the set "mySet". +// result2, err := client.SIsMember("mySet", "nonExistingMember") +// // result2.Value(): false +// // Indicates that "nonExistingMember" does not exist in the set "mySet". +// +// [valkey.io]: https://valkey.io/commands/sismember/ +func (client *Command) SIsMember(key string, member string) (bool, error) { result, err := client.executeCommand(C.SIsMember, []string{key, member}) if err != nil { return defaultBoolResponse, err @@ -643,7 +1758,32 @@ func (client *baseClient) SIsMember(key string, member string) (bool, error) { return handleBoolResponse(result) } -func (client *baseClient) SDiff(keys []string) (map[Result[string]]struct{}, error) { +// SDiff computes the difference between the first set and all the successive sets in keys. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - The keys of the sets to diff. +// +// Return value: +// +// A map[Result[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"): {}, +// // } +// // Indicates that "element" is present in "set1", but missing in "set2" +// +// [valkey.io]: https://valkey.io/commands/sdiff/ +func (client *Command) SDiff(keys []string) (map[Result[string]]struct{}, error) { result, err := client.executeCommand(C.SDiff, keys) if err != nil { return nil, err @@ -652,7 +1792,30 @@ func (client *baseClient) SDiff(keys []string) (map[Result[string]]struct{}, err return handleStringSetResponse(result) } -func (client *baseClient) SDiffStore(destination string, keys []string) (int64, error) { +// SDiffStore stores the difference between the first set and all the successive sets in keys +// into a new set at destination. +// +// Note: When in cluster mode, destination and all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// destination - The key of the destination set. +// keys - The keys of the sets to diff. +// +// Return value: +// +// The number of elements in the resulting set. +// +// Example: +// +// result, err := client.SDiffStore("mySet", []string{"set1", "set2"}) +// // result: 5 +// // Indicates that the resulting set "mySet" contains 5 elements +// +// [valkey.io]: https://valkey.io/commands/sdiffstore/ +func (client *Command) SDiffStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SDiffStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -661,7 +1824,32 @@ func (client *baseClient) SDiffStore(destination string, keys []string) (int64, return handleIntResponse(result) } -func (client *baseClient) SInter(keys []string) (map[Result[string]]struct{}, error) { +// SInter gets the intersection of all the given sets. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// 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. +// +// Example: +// +// result, err := client.SInter([]string{"set1", "set2"}) +// // result might contain: +// // map[Result[string]]struct{}{ +// // api.CreateStringResult("element"): {}, +// // } +// // Indicates that "element" is present in both "set1" and "set2" +// +// [valkey.io]: https://valkey.io/commands/sinter/ +func (client *Command) SInter(keys []string) (map[Result[string]]struct{}, error) { result, err := client.executeCommand(C.SInter, keys) if err != nil { return nil, err @@ -670,7 +1858,31 @@ func (client *baseClient) SInter(keys []string) (map[Result[string]]struct{}, er return handleStringSetResponse(result) } -func (client *baseClient) SInterStore(destination string, keys []string) (int64, error) { +// Stores the members of the intersection of all given sets specified by `keys` into a new set at `destination` +// +// Note: When in cluster mode, `destination` and all `keys` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// destination - The key of the destination set. +// keys - The keys from which to retrieve the set members. +// +// Return value: +// +// The number of elements in the resulting set. +// +// Example: +// +// result, err := client.SInterStore("my_set", []string{"set1", "set2"}) +// if err != nil { +// fmt.Println(result) +// } +// // Output: 2 - Two elements were stored at "my_set", and those elements are the intersection of "set1" and "set2". +// +// [valkey.io]: https://valkey.io/commands/sinterstore/ +func (client *Command) SInterStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SInterStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -679,7 +1891,34 @@ func (client *baseClient) SInterStore(destination string, keys []string) (int64, return handleIntResponse(result) } -func (client *baseClient) SInterCard(keys []string) (int64, error) { +// SInterCard gets the cardinality of the intersection of all the given sets. +// +// Since: +// +// Valkey 7.0 and above. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - The keys of the sets to intersect. +// +// Return value: +// +// The cardinality of the intersection result. If one or more sets do not exist, `0` is returned. +// +// Example: +// +// result, err := client.SInterCard([]string{"set1", "set2"}) +// // result: 2 +// // Indicates that the intersection of "set1" and "set2" contains 2 elements +// result, err := client.SInterCard([]string{"set1", "nonExistingSet"}) +// // result: 0 +// +// [valkey.io]: https://valkey.io/commands/sintercard/ +func (client *Command) SInterCard(keys []string) (int64, error) { result, err := client.executeCommand(C.SInterCard, append([]string{strconv.Itoa(len(keys))}, keys...)) if err != nil { return defaultIntResponse, err @@ -688,7 +1927,36 @@ func (client *baseClient) SInterCard(keys []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) SInterCardLimit(keys []string, limit int64) (int64, error) { +// SInterCardLimit gets the cardinality of the intersection of all the given sets, up to the specified limit. +// +// Since: +// +// Valkey 7.0 and above. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - The keys of the sets to intersect. +// limit - The limit for the intersection cardinality value. +// +// Return value: +// +// The cardinality of the intersection result, or the limit if reached. +// If one or more sets do not exist, `0` is returned. +// If the intersection cardinality reaches 'limit' partway through the computation, returns 'limit' as the cardinality. +// +// Example: +// +// result, err := client.SInterCardLimit([]string{"set1", "set2"}, 3) +// // result: 2 +// // Indicates that the intersection of "set1" and "set2" contains 2 elements (or at least 3 if the actual +// // intersection is larger) +// +// [valkey.io]: https://valkey.io/commands/sintercard/ +func (client *Command) SInterCardLimit(keys []string, limit int64) (int64, error) { args := utils.Concat([]string{utils.IntToString(int64(len(keys)))}, keys, []string{"LIMIT", utils.IntToString(limit)}) result, err := client.executeCommand(C.SInterCard, args) @@ -699,7 +1967,28 @@ func (client *baseClient) SInterCardLimit(keys []string, limit int64) (int64, er return handleIntResponse(result) } -func (client *baseClient) SRandMember(key string) (Result[string], error) { +// SRandMember returns a random element from the set value stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key from which to retrieve the set member. +// +// Return value: +// +// A Result[string] containing a random element from the set. +// Returns api.CreateNilStringResult() if key does not exist. +// +// Example: +// +// client.SAdd("test", []string{"one"}) +// response, err := client.SRandMember("test") +// // response.Value(): "one" +// // err: nil +// +// [valkey.io]: https://valkey.io/commands/srandmember/ +func (client *Command) SRandMember(key string) (Result[string], error) { result, err := client.executeCommand(C.SRandMember, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -708,7 +1997,30 @@ func (client *baseClient) SRandMember(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) SPop(key string) (Result[string], error) { +// SPop removes and returns one random member from the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the set. +// +// Return value: +// +// A Result[string] containing the value of the popped member. +// Returns a NilResult if key does not exist. +// +// Example: +// +// value1, err := client.SPop("mySet") +// // value1.Value() might be "value1" +// // err: nil +// value2, err := client.SPop("nonExistingSet") +// // value2.IsNil(): true +// // err: nil +// +// [valkey.io]: https://valkey.io/commands/spop/ +func (client *Command) SPop(key string) (Result[string], error) { result, err := client.executeCommand(C.SPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -717,7 +2029,31 @@ func (client *baseClient) SPop(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) SMIsMember(key string, members []string) ([]bool, error) { +// SMIsMember returns whether each member is a member of the set stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the set. +// +// Return value: +// +// A []bool containing whether each member is a member of the set stored at key. +// +// Example: +// +// client.SAdd("myKey", []string{"one", "two"}) +// value1, err := client.SMIsMember("myKey", []string{"two", "three"}) +// // value1[0].Value(): true +// // value1[1].Value(): false +// // err: nil +// value2, err := client.SPop("nonExistingKey", []string{"one"}) +// // value2[0].Value(): false +// // err: nil +// +// [valkey.io]: https://valkey.io/commands/smismember/ +func (client *Command) SMIsMember(key string, members []string) ([]bool, error) { result, err := client.executeCommand(C.SMIsMember, append([]string{key}, members...)) if err != nil { return nil, err @@ -726,7 +2062,41 @@ func (client *baseClient) SMIsMember(key string, members []string) ([]bool, erro return handleBoolArrayResponse(result) } -func (client *baseClient) SUnion(keys []string) (map[Result[string]]struct{}, error) { +// SUnion gets the union of all the given sets. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// 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. +// +// Example: +// +// result1, err := client.SAdd("my_set1", []string {"member1", "member2"}) +// // result.Value(): 2 +// // result.IsNil(): false +// +// result2, err := client.SAdd("my_set2", []string {"member2", "member3"}) +// // result.Value(): 2 +// // result.IsNil(): false +// +// result3, err := client.SUnion([]string {"my_set1", "my_set2"}) +// // result3.Value(): "{'member1', 'member2', 'member3'}" +// // err: nil +// +// result4, err := client.SUnion([]string {"my_set1", "non_existing_set"}) +// // result4.Value(): "{'member1', 'member2'}" +// // err: nil +// +// [valkey.io]: https://valkey.io/commands/sunion/ +func (client *Command) SUnion(keys []string) (map[Result[string]]struct{}, error) { result, err := client.executeCommand(C.SUnion, keys) if err != nil { return nil, err @@ -735,7 +2105,44 @@ func (client *baseClient) SUnion(keys []string) (map[Result[string]]struct{}, er return handleStringSetResponse(result) } -func (client *baseClient) SScan(key string, cursor string) (Result[string], []Result[string], error) { +// Iterates incrementally over a set. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the set. +// cursor - The cursor that points to the next iteration of results. +// A value of `"0"` indicates the start of the search. +// For Valkey 8.0 and above, negative cursors are treated like the initial cursor("0"). +// +// Return value: +// +// An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and +// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the set. +// The second element is always an array of the subset of the set held in `key`. +// +// Example: +// +// // assume "key" contains a set +// resCursor, resCol, err := client.sscan("key", "0") +// for resCursor != "0" { +// resCursor, resCol, err = client.sscan("key", "0") +// fmt.Println("Cursor: ", resCursor.Value()) +// fmt.Println("Members: ", resCol.Value()) +// } +// // Output: +// // Cursor: 48 +// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] +// // Cursor: 24 +// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] +// // Cursor: 0 +// // Members: ['47', '122', '1', '53', '10', '14', '80'] +// +// [valkey.io]: https://valkey.io/commands/sscan/ +func (client *Command) SScan(key string, cursor string) (Result[string], []Result[string], error) { result, err := client.executeCommand(C.SScan, []string{key, cursor}) if err != nil { return CreateNilStringResult(), nil, err @@ -743,7 +2150,46 @@ func (client *baseClient) SScan(key string, cursor string) (Result[string], []Re return handleScanResponse(result) } -func (client *baseClient) SScanWithOptions( +// Iterates incrementally over a set. +// +// Note: When in cluster mode, all keys must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the set. +// cursor - The cursor that points to the next iteration of results. +// A value of `"0"` indicates the start of the search. +// For Valkey 8.0 and above, negative cursors are treated like the initial cursor("0"). +// options - [options.BaseScanOptions] +// +// Return value: +// +// An array of the cursor and the subset of the set held by `key`. The first element is always the `cursor` and +// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the set. +// The second element is always an array of the subset of the set held in `key`. +// +// Example: +// +// // assume "key" contains a set +// resCursor resCol, err := client.sscan("key", "0", opts) +// for resCursor != "0" { +// opts := options.NewBaseScanOptionsBuilder().SetMatch("*") +// resCursor, resCol, err = client.sscan("key", "0", opts) +// fmt.Println("Cursor: ", resCursor.Value()) +// fmt.Println("Members: ", resCol.Value()) +// } +// // Output: +// // Cursor: 48 +// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] +// // Cursor: 24 +// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] +// // Cursor: 0 +// // Members: ['47', '122', '1', '53', '10', '14', '80'] +// +// [valkey.io]: https://valkey.io/commands/sscan/ +func (client *Command) SScanWithOptions( key string, cursor string, options *options.BaseScanOptions, @@ -760,7 +2206,30 @@ func (client *baseClient) SScanWithOptions( return handleScanResponse(result) } -func (client *baseClient) SMove(source string, destination string, member string) (bool, error) { +// Moves `member` from the set at `source` to the set at `destination`, removing it from the source set. +// Creates a new destination set if needed. The operation is atomic. +// +// Note: When in cluster mode, `source` and `destination` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// source - The key of the set to remove the element from. +// destination - The key of the set to add the element to. +// member - The set element to move. +// +// Return value: +// +// `true` on success, or `false` if the `source` set does not exist or the element is not a member of the source set. +// +// Example: +// +// moved := SMove("set1", "set2", "element") +// fmt.Println(moved.Value()) // Output: true +// +// [valkey.io]: https://valkey.io/commands/smove/ +func (client *Command) SMove(source string, destination string, member string) (bool, error) { result, err := client.executeCommand(C.SMove, []string{source, destination, member}) if err != nil { return defaultBoolResponse, err @@ -768,7 +2237,38 @@ func (client *baseClient) SMove(source string, destination string, member string return handleBoolResponse(result) } -func (client *baseClient) LRange(key string, start int64, end int64) ([]Result[string], error) { +// Returns the specified elements of the list 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. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// start - The starting point of the range. +// end - The end of the range. +// +// Return value: +// +// Array of elements as Result[string] in the specified range. +// If start exceeds the end of the list, or if start is greater than end, an empty array will be returned. +// If end exceeds the actual end of the list, the range will stop at the actual end of the list. +// If key does not exist an empty array will be returned. +// +// For example: +// 1. result, err := client.LRange("my_list", 0, 2) +// +// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2"), +// api.CreateStringResult("value3")} +// 2. result, err := client.LRange("my_list", -2, -1) +// result: []api.Result[string]{api.CreateStringResult("value2"), api.CreateStringResult("value3")} +// 3. result, err := client.LRange("non_existent_key", 0, 2) +// result: []api.Result[string]{} +// +// [valkey.io]: https://valkey.io/commands/lrange/ +func (client *Command) LRange(key string, start int64, end int64) ([]Result[string], error) { result, err := client.executeCommand(C.LRange, []string{key, utils.IntToString(start), utils.IntToString(end)}) if err != nil { return nil, err @@ -777,7 +2277,33 @@ func (client *baseClient) LRange(key string, start int64, end int64) ([]Result[s return handleStringArrayResponse(result) } -func (client *baseClient) LIndex(key string, index int64) (Result[string], error) { +// Returns the element at index from the list stored at key. +// The index is zero-based, so 0 means the first element, 1 the second element and so on. Negative indices can be used to +// designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so +// forth. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// index - The index of the element in the list to retrieve. +// +// Return value: +// +// The Result[string] containing element at index in the list stored at key. +// If index is out of range or if key does not exist, [api.CreateNilStringResult()] is returned. +// +// For example: +// 1. result, err := client.LIndex("myList", 0) +// result.Value(): "value1" // Returns the first element in the list stored at 'myList'. +// result.IsNil(): false +// 2. result, err := client.LIndex("myList", -1) +// result.Value(): "value3" // Returns the last element in the list stored at 'myList'. +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/lindex/ +func (client *Command) LIndex(key string, index int64) (Result[string], error) { result, err := client.executeCommand(C.LIndex, []string{key, utils.IntToString(index)}) if err != nil { return CreateNilStringResult(), err @@ -786,7 +2312,34 @@ func (client *baseClient) LIndex(key string, index int64) (Result[string], error return handleStringOrNilResponse(result) } -func (client *baseClient) LTrim(key string, start int64, end int64) (string, error) { +// Trims an existing list so that it will contain only the specified range of elements specified. +// 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. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// start - The starting point of the range. +// end - The end of the range. +// +// Return value: +// +// Always `"OK"`. +// If start exceeds the end of the list, or if start is greater than end, the result will be an empty list (which causes +// key to be removed). +// If end exceeds the actual end of the list, it will be treated like the last element of the list. +// If key does not exist, `"OK"` will be returned without changes to the database. +// +// For example: +// +// result, err := client.LTrim("my_list", 0, 1) +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/ltrim/ +func (client *Command) LTrim(key string, start int64, end int64) (string, error) { result, err := client.executeCommand(C.LTrim, []string{key, utils.IntToString(start), utils.IntToString(end)}) if err != nil { return defaultStringResponse, err @@ -795,7 +2348,26 @@ func (client *baseClient) LTrim(key string, start int64, end int64) (string, err return handleStringResponse(result) } -func (client *baseClient) LLen(key string) (int64, error) { +// Returns the length of the list stored at key. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// +// Return value: +// +// The length of the list at `key`. +// If `key` does not exist, it is interpreted as an empty list and `0` is returned. +// +// For example: +// +// result, err := client.LLen("my_list") +// result: 3 // Indicates that there are 3 elements in the list. +// +// [valkey.io]: https://valkey.io/commands/llen/ +func (client *Command) LLen(key string) (int64, error) { result, err := client.executeCommand(C.LLen, []string{key}) if err != nil { return defaultIntResponse, err @@ -804,7 +2376,32 @@ func (client *baseClient) LLen(key string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) LRem(key string, count int64, element string) (int64, error) { +// Removes the first count occurrences of elements equal to element from the list stored at key. +// If count is positive: Removes elements equal to element moving from head to tail. +// If count is negative: Removes elements equal to element moving from tail to head. +// If count is 0 or count is greater than the occurrences of elements equal to element, it removes all elements equal to +// element. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// count - The count of the occurrences of elements equal to element to remove. +// element - The element to remove from the list. +// +// Return value: +// +// The number of the removed elements. +// If `key` does not exist, `0` is returned. +// +// For example: +// +// result, err := client.LRem("my_list", 2, "value") +// result: 2 +// +// [valkey.io]: https://valkey.io/commands/lrem/ +func (client *Command) LRem(key string, count int64, element string) (int64, error) { result, err := client.executeCommand(C.LRem, []string{key, utils.IntToString(count), element}) if err != nil { return defaultIntResponse, err @@ -813,7 +2410,30 @@ func (client *baseClient) LRem(key string, count int64, element string) (int64, return handleIntResponse(result) } -func (client *baseClient) RPop(key string) (Result[string], error) { +// Removes and returns the last elements of the list stored at key. +// The command pops a single element from the end of the list. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// +// Return value: +// +// The Result[string] containing the value of the last element. +// If key does not exist, [api.CreateNilStringResult()] will be returned. +// +// For example: +// 1. result, err := client.RPop("my_list") +// result.Value(): "value1" +// result.IsNil(): false +// 2. result, err := client.RPop("non_exiting_key") +// result.Value(): "" +// result.IsNil(): true +// +// [valkey.io]: https://valkey.io/commands/rpop/ +func (client *Command) RPop(key string) (Result[string], error) { result, err := client.executeCommand(C.RPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -822,7 +2442,28 @@ func (client *baseClient) RPop(key string) (Result[string], error) { return handleStringOrNilResponse(result) } -func (client *baseClient) RPopCount(key string, count int64) ([]Result[string], error) { +// Removes and returns up to count elements from the list stored at key, depending on the list's length. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// count - The count of the elements to pop from the list. +// +// Return value: +// +// An array of popped elements as Result[string] will be returned depending on the list's length. +// If key does not exist, nil will be returned. +// +// For example: +// 1. result, err := client.RPopCount("my_list", 2) +// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")} +// 2. result, err := client.RPop("non_exiting_key") +// result: nil +// +// [valkey.io]: https://valkey.io/commands/rpop/ +func (client *Command) RPopCount(key string, count int64) ([]Result[string], error) { result, err := client.executeCommand(C.RPop, []string{key, utils.IntToString(count)}) if err != nil { return nil, err @@ -831,7 +2472,31 @@ func (client *baseClient) RPopCount(key string, count int64) ([]Result[string], return handleStringArrayOrNullResponse(result) } -func (client *baseClient) LInsert( +// Inserts element in the list at key either before or after the pivot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// insertPosition - The relative position to insert into - either api.Before or api.After the pivot. +// pivot - An element of the list. +// element - The new element to insert. +// +// Return value: +// +// The list length after a successful insert operation. +// If the `key` doesn't exist returns `-1`. +// If the `pivot` wasn't found, returns `0`. +// +// For example: +// +// "my_list": {"Hello", "Wprld"} +// result, err := client.LInsert("my_list", api.Before, "World", "There") +// result: 3 +// +// [valkey.io]: https://valkey.io/commands/linsert/ +func (client *Command) LInsert( key string, insertPosition InsertPosition, pivot string, @@ -853,7 +2518,35 @@ func (client *baseClient) LInsert( return handleIntResponse(result) } -func (client *baseClient) BLPop(keys []string, timeoutSecs float64) ([]Result[string], error) { +// Pops an element from the head of the first list that is non-empty, with the given keys being checked in the order that +// they are given. +// Blocks the connection when there are no elements to pop from any of the given lists. +// +// Note: +// - When in cluster mode, all keys must map to the same hash slot. +// - BLPop is a client blocking command, see [Blocking Commands] for more details and best practices. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - The keys of the lists to pop from. +// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block indefinitely. +// +// Return value: +// +// A two-element array of Result[string] containing the key from which the element was popped and the value of the popped +// element, formatted as [key, value]. +// If no element could be popped and the timeout expired, returns nil. +// +// For example: +// +// result, err := client.BLPop("list1", "list2", 0.5) +// result: []api.Result[string]{api.CreateStringResult("list1"), api.CreateStringResult("element")} +// +// [valkey.io]: https://valkey.io/commands/blpop/ +// [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands +func (client *Command) BLPop(keys []string, timeoutSecs float64) ([]Result[string], error) { result, err := client.executeCommand(C.BLPop, append(keys, utils.FloatToString(timeoutSecs))) if err != nil { return nil, err @@ -862,7 +2555,35 @@ func (client *baseClient) BLPop(keys []string, timeoutSecs float64) ([]Result[st return handleStringArrayOrNullResponse(result) } -func (client *baseClient) BRPop(keys []string, timeoutSecs float64) ([]Result[string], error) { +// Pops an element from the tail of the first list that is non-empty, with the given keys being checked in the order that +// they are given. +// Blocks the connection when there are no elements to pop from any of the given lists. +// +// Note: +// - When in cluster mode, all keys must map to the same hash slot. +// - BRPop is a client blocking command, see [Blocking Commands] for more details and best practices. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - The keys of the lists to pop from. +// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block indefinitely. +// +// Return value: +// +// A two-element array of Result[string] containing the key from which the element was popped and the value of the popped +// element, formatted as [key, value]. +// If no element could be popped and the timeoutSecs expired, returns nil. +// +// For example: +// +// result, err := client.BRPop("list1", "list2", 0.5) +// result: []api.Result[string]{api.CreateStringResult("list1"), api.CreateStringResult("element")} +// +// [valkey.io]: https://valkey.io/commands/brpop/ +// [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands +func (client *Command) BRPop(keys []string, timeoutSecs float64) ([]Result[string], error) { result, err := client.executeCommand(C.BRPop, append(keys, utils.FloatToString(timeoutSecs))) if err != nil { return nil, err @@ -871,7 +2592,28 @@ func (client *baseClient) BRPop(keys []string, timeoutSecs float64) ([]Result[st return handleStringArrayOrNullResponse(result) } -func (client *baseClient) RPushX(key string, elements []string) (int64, error) { +// Inserts all the specified values at the tail of the list stored at key, only if key exists and holds a list. If key is +// not a list, this performs no operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// elements - The elements to insert at the tail of the list stored at key. +// +// Return value: +// +// The length of the list after the push operation. +// +// For example: +// +// my_list: {"value1", "value2"} +// result, err := client.RPushX("my_list", []string{"value3", value4}) +// result: 4 +// +// [valkey.io]: https://valkey.io/commands/rpushx/ +func (client *Command) RPushX(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.RPushX, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -880,7 +2622,28 @@ func (client *baseClient) RPushX(key string, elements []string) (int64, error) { return handleIntResponse(result) } -func (client *baseClient) LPushX(key string, elements []string) (int64, error) { +// Inserts all the specified values at the head of the list stored at key, only if key exists and holds a list. If key is +// not a list, this performs no operation. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// elements - The elements to insert at the head of the list stored at key. +// +// Return value: +// +// The length of the list after the push operation. +// +// For example: +// +// my_list: {"value1", "value2"} +// result, err := client.LPushX("my_list", []string{"value3", value4}) +// result: 4 +// +// [valkey.io]: https://valkey.io/commands/rpushx/ +func (client *Command) LPushX(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.LPushX, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -889,7 +2652,31 @@ 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) { +// Pops one element from the first non-empty list from the provided keys. +// +// Since: +// +// Valkey 7.0 and above. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - An array of keys to lists. +// listDirection - The direction based on which elements are popped from - see [api.ListDirection]. +// +// Return value: +// +// A map of key name mapped array of popped element. +// +// 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")} +// +// [valkey.io]: https://valkey.io/commands/lmpop/ +func (client *Command) LMPop(keys []string, listDirection ListDirection) (map[Result[string]][]Result[string], error) { listDirectionStr, err := listDirection.toString() if err != nil { return nil, err @@ -913,7 +2700,32 @@ func (client *baseClient) LMPop(keys []string, listDirection ListDirection) (map return handleStringToStringArrayMapOrNullResponse(result) } -func (client *baseClient) LMPopCount( +// Pops one or more elements from the first non-empty list from the provided keys. +// +// Since: +// +// Valkey 7.0 and above. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - An array of keys to lists. +// listDirection - The direction based on which elements are popped from - see [api.ListDirection]. +// count - The maximum number of popped elements. +// +// Return value: +// +// A map of key name mapped array of popped elements. +// +// 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")} +// +// [valkey.io]: https://valkey.io/commands/lmpop/ +func (client *Command) LMPopCount( keys []string, listDirection ListDirection, count int64, @@ -941,7 +2753,40 @@ func (client *baseClient) LMPopCount( return handleStringToStringArrayMapOrNullResponse(result) } -func (client *baseClient) BLMPop( +// 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]. +// +// Note: +// - When in cluster mode, all keys must map to the same hash slot. +// - BLMPop is a client blocking command, see [Blocking Commands] for more details and best practices. +// +// Since: +// +// Valkey 7.0 and above. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - An array of keys to lists. +// listDirection - The direction based on which elements are popped from - see [api.ListDirection]. +// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block +// indefinitely. +// +// Return value: +// +// A map of key name mapped array of popped element. +// If no member could be popped and the timeout expired, returns nil. +// +// 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")} +// +// [valkey.io]: https://valkey.io/commands/blmpop/ +// [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands +func (client *Command) BLMPop( keys []string, listDirection ListDirection, timeoutSecs float64, @@ -969,7 +2814,42 @@ func (client *baseClient) BLMPop( return handleStringToStringArrayMapOrNullResponse(result) } -func (client *baseClient) BLMPopCount( +// 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]. +// +// Note: +// - When in cluster mode, all keys must map to the same hash slot. +// - BLMPopCount is a client blocking command, see [Blocking Commands] for more details and best practices. +// +// Since: +// +// Valkey 7.0 and above. +// +// See [valkey.io] for details. +// +// Parameters: +// +// keys - An array of keys to lists. +// listDirection - The direction based on which elements are popped from - see [api.ListDirection]. +// count - The maximum number of popped elements. +// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block +// +// indefinitely. +// +// Return value: +// +// A map of key name mapped array of popped element. +// If no member could be popped and the timeout expired, returns nil. +// +// 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")} +// +// [valkey.io]: https://valkey.io/commands/blmpop/ +// [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands +func (client *Command) BLMPopCount( keys []string, listDirection ListDirection, count int64, @@ -998,7 +2878,30 @@ func (client *baseClient) BLMPopCount( return handleStringToStringArrayMapOrNullResponse(result) } -func (client *baseClient) LSet(key string, index int64, element 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 +// designate elements starting at the tail of the list. Here, -1 means the last element, -2 means the penultimate and so +// forth. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the list. +// index - The index of the element in the list to be set. +// element - The element to be set. +// +// Return value: +// +// `"OK"`. +// +// For example: +// +// result, err: client.LSet("my_list", int64(1), "two") +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/lset/ +func (client *Command) LSet(key string, index int64, element string) (string, error) { result, err := client.executeCommand(C.LSet, []string{key, utils.IntToString(index), element}) if err != nil { return defaultStringResponse, err @@ -1007,7 +2910,36 @@ func (client *baseClient) LSet(key string, index int64, element string) (string, return handleStringResponse(result) } -func (client *baseClient) LMove( +// Atomically pops and removes the left/right-most element to the list stored at source depending on whereFrom, and pushes +// the element at the first/last element of the list stored at destination depending on whereTo. +// +// See [valkey.io] for details. +// +// Parameters: +// +// source - The key to the source list. +// destination - The key to the destination list. +// wherefrom - The ListDirection the element should be removed from. +// whereto - The ListDirection the element should be added to. +// +// Return value: +// +// A Result[string] containing the popped element or api.CreateNilStringResult() if source does not exist. +// +// For example: +// +// result, err: client.LPush("my_list", []string{"two", "one"}) +// result, err: client.LPush("my_list2", []string{"four", "three"}) +// result, err: client.LMove("my_list1", "my_list2", api.Left, api.Left) +// result.Value(): "one" +// updatedList1, err: client.LRange("my_list1", int64(0), int64(-1)) +// updatedList2, err: client.LRange("my_list2", int64(0), int64(-1)) +// updatedList1: []api.Result[string]{api.CreateStringResult("two")} +// updatedList2: []api.Result[string]{api.CreateStringResult("one"), api.CreateStringResult("three"), +// api.CreateStringResult("four")} +// +// [valkey.io]: https://valkey.io/commands/lmove/ +func (client *Command) LMove( source string, destination string, whereFrom ListDirection, @@ -1030,7 +2962,48 @@ func (client *baseClient) LMove( return handleStringOrNilResponse(result) } -func (client *baseClient) BLMove( +// Blocks the connection until it pops atomically and removes the left/right-most element to the list stored at source +// depending on whereFrom, and pushes the element at the first/last element of the list stored at Date: Thu, 16 Jan 2025 18:08:34 -0500 Subject: [PATCH 02/21] GO: xpending command (#2957) * GO: add xpending command Signed-off-by: jbrinkman Signed-off-by: Edward Liang --- go/Makefile | 6 +- go/api/base_client.go | 90 +++++ go/api/options/stream_options.go | 54 +++ go/api/response_handlers.go | 92 +++++ go/api/response_types.go | 47 +++ go/api/stream_commands.go | 4 + go/integTest/shared_commands_test.go | 508 +++++++++++++++++++++++++++ 7 files changed, 798 insertions(+), 3 deletions(-) diff --git a/go/Makefile b/go/Makefile index 62eabbaa8b..014bf962a4 100644 --- a/go/Makefile +++ b/go/Makefile @@ -82,15 +82,15 @@ unit-test: mkdir -p reports set -o pipefail; \ LD_LIBRARY_PATH=$(shell find . -name libglide_rs.so|grep -w release|tail -1|xargs dirname|xargs readlink -f):${LD_LIBRARY_PATH} \ - go test -v -race ./... -skip TestGlideTestSuite $(if $(test-filter), -run $(test-filter)) \ + go test -v -race ./... -skip TestGlideTestSuite $(if $(test-filter), -testify.m $(test-filter)) \ | tee >(go tool test2json -t -p github.com/valkey-io/valkey-glide/go/glide/utils | go-test-report -o reports/unit-tests.html -t unit-test > /dev/null) # integration tests - run subtask with skipping modules tests -integ-test: export TEST_FILTER = -skip TestGlideTestSuite/TestModule $(if $(test-filter), -run $(test-filter)) +integ-test: export TEST_FILTER = -skip TestGlideTestSuite/TestModule $(if $(test-filter), -testify.m $(test-filter)) integ-test: __it # modules tests - run substask with default filter -modules-test: export TEST_FILTER = $(if $(test-filter), -run $(test-filter), -run TestGlideTestSuite/TestModule) +modules-test: export TEST_FILTER = $(if $(test-filter), -run $(test-filter), -testify.m TestGlideTestSuite/TestModule) modules-test: __it __it: diff --git a/go/api/base_client.go b/go/api/base_client.go index b19fbebb16..ffda2f08f1 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -4857,3 +4857,93 @@ func (client *baseClient) ZScanWithOptions( } return handleScanResponse(result) } + +// Returns stream message summary information for pending messages matching a stream and group. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// +// Return value: +// An XPendingSummary struct that includes a summary with the following fields: +// +// NumOfMessages: The total number of pending messages for this consumer group. +// StartId: The smallest ID among the pending messages or nil if no pending messages exist. +// EndId: The greatest ID among the pending messages or nil if no pending messages exists. +// GroupConsumers: An array of ConsumerPendingMessages with the following fields: +// ConsumerName: The name of the consumer. +// MessageCount: The number of pending messages for this consumer. +// +// Example +// +// result, err := client.XPending("myStream", "myGroup") +// if err != nil { +// return err +// } +// fmt.Println("Number of pending messages: ", result.NumOfMessages) +// fmt.Println("Start and End ID of messages: ", result.StartId, result.EndId) +// for _, consumer := range result.ConsumerMessages { +// fmt.Printf("Consumer messages: %s: $v\n", consumer.ConsumerName, consumer.MessageCount) +// } +// +// [valkey.io]: https://valkey.io/commands/xpending/ +func (client *baseClient) XPending(key string, group string) (XPendingSummary, error) { + result, err := client.executeCommand(C.XPending, []string{key, group}) + if err != nil { + return XPendingSummary{}, err + } + + return handleXPendingSummaryResponse(result) +} + +// Returns stream message summary information for pending messages matching a given range of IDs. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// opts - The options for the command. See [options.XPendingOptions] for details. +// +// Return value: +// A slice of XPendingDetail structs, where each detail struct includes the following fields: +// +// Id - The ID of the pending message. +// ConsumerName - The name of the consumer that fetched the message and has still to acknowledge it. +// IdleTime - The time in milliseconds since the last time the message was delivered to the consumer. +// DeliveryCount - The number of times this message was delivered. +// +// Example +// +// detailResult, err := client.XPendingWithOptions(key, groupName, options.NewXPendingOptions("-", "+", 10)) +// if err != nil { +// return err +// } +// fmt.Println("=========================") +// for _, detail := range detailResult { +// fmt.Println(detail.Id) +// fmt.Println(detail.ConsumerName) +// fmt.Println(detail.IdleTime) +// fmt.Println(detail.DeliveryCount) +// fmt.Println("=========================") +// } +// +// [valkey.io]: https://valkey.io/commands/xpending/ +func (client *baseClient) XPendingWithOptions( + key string, + group string, + opts *options.XPendingOptions, +) ([]XPendingDetail, error) { + optionArgs, _ := opts.ToArgs() + args := append([]string{key, group}, optionArgs...) + + result, err := client.executeCommand(C.XPending, args) + if err != nil { + return nil, err + } + return handleXPendingDetailResponse(result) +} diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index 2d2f2318a2..19f5e6d5c4 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -149,3 +149,57 @@ func (xro *XReadOptions) ToArgs() ([]string, error) { } return args, nil } + +// Optional arguments for `XPending` in [StreamCommands] +type XPendingOptions struct { + minIdleTime int64 + start string + end string + count int64 + consumer string +} + +// Create new empty `XPendingOptions`. The `start`, `end` and `count` arguments are required. +func NewXPendingOptions(start string, end string, count int64) *XPendingOptions { + options := &XPendingOptions{} + options.start = start + options.end = end + options.count = count + return options +} + +// SetMinIdleTime sets the minimum idle time for the XPendingOptions. +// minIdleTime is the amount of time (in milliseconds) that a message must be idle to be considered. +// It returns the updated XPendingOptions. +func (xpo *XPendingOptions) SetMinIdleTime(minIdleTime int64) *XPendingOptions { + xpo.minIdleTime = minIdleTime + return xpo +} + +// SetConsumer sets the consumer for the XPendingOptions. +// consumer is the name of the consumer to filter the pending messages. +// It returns the updated XPendingOptions. +func (xpo *XPendingOptions) SetConsumer(consumer string) *XPendingOptions { + xpo.consumer = consumer + return xpo +} + +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)) + } + + args = append(args, xpo.start) + args = append(args, xpo.end) + args = append(args, utils.IntToString(xpo.count)) + + if xpo.consumer != "" { + args = append(args, xpo.consumer) + } + + return args, nil +} diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index adfba889b1..07ab7e1a09 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -9,6 +9,7 @@ import "C" import ( "fmt" "reflect" + "strconv" "unsafe" ) @@ -601,3 +602,94 @@ func handleXReadResponse(response *C.struct_CommandResponse) (map[string]map[str } return nil, &RequestError{fmt.Sprintf("unexpected type received: %T", res)} } + +func handleXPendingSummaryResponse(response *C.struct_CommandResponse) (XPendingSummary, error) { + defer C.free_command_response(response) + + typeErr := checkResponseType(response, C.Array, true) + if typeErr != nil { + return CreateNilXPendingSummary(), typeErr + } + + slice, err := parseArray(response) + if err != nil { + return CreateNilXPendingSummary(), err + } + + arr := slice.([]interface{}) + NumOfMessages := arr[0].(int64) + var StartId, EndId Result[string] + if arr[1] == nil { + StartId = CreateNilStringResult() + } else { + StartId = CreateStringResult(arr[1].(string)) + } + if arr[2] == nil { + EndId = CreateNilStringResult() + } else { + EndId = CreateStringResult(arr[2].(string)) + } + + if pendingMessages, ok := arr[3].([]interface{}); ok { + var ConsumerPendingMessages []ConsumerPendingMessage + for _, msg := range pendingMessages { + consumerMessage := msg.([]interface{}) + count, err := strconv.ParseInt(consumerMessage[1].(string), 10, 64) + if err == nil { + ConsumerPendingMessages = append(ConsumerPendingMessages, ConsumerPendingMessage{ + ConsumerName: consumerMessage[0].(string), + MessageCount: count, + }) + } + } + return XPendingSummary{NumOfMessages, StartId, EndId, ConsumerPendingMessages}, nil + } else { + return XPendingSummary{NumOfMessages, StartId, EndId, make([]ConsumerPendingMessage, 0)}, nil + } +} + +func handleXPendingDetailResponse(response *C.struct_CommandResponse) ([]XPendingDetail, error) { + // response should be [][]interface{} + + defer C.free_command_response(response) + + // TODO: Not sure if this is correct for a nill response + if response == nil || response.response_type == uint32(C.Null) { + return make([]XPendingDetail, 0), nil + } + + typeErr := checkResponseType(response, C.Array, true) + if typeErr != nil { + return make([]XPendingDetail, 0), typeErr + } + + // parse first level of array + slice, err := parseArray(response) + arr := slice.([]interface{}) + + if err != nil { + return make([]XPendingDetail, 0), err + } + + pendingDetails := make([]XPendingDetail, 0, len(arr)) + + for _, message := range arr { + switch detail := message.(type) { + case []interface{}: + pDetail := XPendingDetail{ + Id: detail[0].(string), + ConsumerName: detail[1].(string), + IdleTime: detail[2].(int64), + DeliveryCount: detail[3].(int64), + } + pendingDetails = append(pendingDetails, pDetail) + + case XPendingDetail: + pendingDetails = append(pendingDetails, detail) + default: + fmt.Printf("handleXPendingDetailResponse - unhandled type: %s\n", reflect.TypeOf(detail)) + } + } + + return pendingDetails, nil +} diff --git a/go/api/response_types.go b/go/api/response_types.go index 2c7f3244b8..2e6e527c43 100644 --- a/go/api/response_types.go +++ b/go/api/response_types.go @@ -146,3 +146,50 @@ func CreateEmptyClusterValue() ClusterValue[interface{}] { value: Result[interface{}]{val: empty, isNil: true}, } } + +// XPendingSummary represents a summary of pending messages in a stream group. +// It includes the total number of pending messages, the ID of the first and last pending messages, +// and a list of consumer pending messages. +type XPendingSummary struct { + // NumOfMessages is the total number of pending messages in the stream group. + NumOfMessages int64 + + // StartId is the ID of the first pending message in the stream group. + StartId Result[string] + + // EndId is the ID of the last pending message in the stream group. + EndId Result[string] + + // ConsumerMessages is a list of pending messages for each consumer in the stream group. + ConsumerMessages []ConsumerPendingMessage +} + +// ConsumerPendingMessage represents a pending message for a consumer in a Redis stream group. +// It includes the consumer's name and the count of pending messages for that consumer. +type ConsumerPendingMessage struct { + // ConsumerName is the name of the consumer. + ConsumerName string + + // MessageCount is the number of pending messages for the consumer. + MessageCount int64 +} + +// XPendingDetail represents the details of a pending message in a stream group. +// It includes the message ID, the consumer's name, the idle time, and the delivery count. +type XPendingDetail struct { + // Id is the ID of the pending message. + Id string + + // ConsumerName is the name of the consumer who has the pending message. + ConsumerName string + + // IdleTime is the amount of time (in milliseconds) that the message has been idle. + IdleTime int64 + + // DeliveryCount is the number of times the message has been delivered. + DeliveryCount int64 +} + +func CreateNilXPendingSummary() XPendingSummary { + return XPendingSummary{0, CreateNilStringResult(), CreateNilStringResult(), make([]ConsumerPendingMessage, 0)} +} diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 35a6c66db3..0e20543902 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -24,4 +24,8 @@ type StreamCommands interface { XReadWithOptions(keysAndIds map[string]string, options *options.XReadOptions) (map[string]map[string][][]string, error) XDel(key string, ids []string) (int64, error) + + XPending(key string, group string) (XPendingSummary, error) + + XPendingWithOptions(key string, group string, options *options.XPendingOptions) ([]XPendingDetail, error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index ce9f7d5309..be729bdad4 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5208,3 +5208,511 @@ func (suite *GlideTestSuite) TestZScan() { assert.IsType(suite.T(), &api.RequestError{}, err) }) } + +func (suite *GlideTestSuite) TestXPending() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // TODO: Update tests when XGroupCreate, XGroupCreateConsumer, XReadGroup, XClaim, XClaimJustId and XAck are added to + // the Go client. + // + // This test splits out the cluster and standalone tests into their own functions because we are forced to use + // CustomCommands for many stream commands which are not included in the preview Go client. Using a type switch for + // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be + // collapsed once the native commands are added in a subsequent release. + + execStandalone := func(client api.GlideClient) { + // 1. Arrange the data + key := uuid.New().String() + groupName := "group" + uuid.New().String() + zeroStreamId := "0" + consumer1 := "consumer-1-" + uuid.New().String() + consumer2 := "consumer-2-" + uuid.New().String() + + command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + + resp, err := client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", resp.(string)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.(bool)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer2} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.(bool)) + + streamid_1, err := client.XAdd(key, [][]string{{"field1", "value1"}}) + assert.NoError(suite.T(), err) + streamid_2, err := client.XAdd(key, [][]string{{"field2", "value2"}}) + assert.NoError(suite.T(), err) + + command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + _, err = client.XAdd(key, [][]string{{"field3", "value3"}}) + assert.NoError(suite.T(), err) + _, err = client.XAdd(key, [][]string{{"field4", "value4"}}) + assert.NoError(suite.T(), err) + streamid_5, err := client.XAdd(key, [][]string{{"field5", "value5"}}) + assert.NoError(suite.T(), err) + + command = []string{"XReadGroup", "GROUP", groupName, consumer2, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + expectedSummary := api.XPendingSummary{ + NumOfMessages: 5, + StartId: streamid_1, + EndId: streamid_5, + ConsumerMessages: []api.ConsumerPendingMessage{ + {ConsumerName: consumer1, MessageCount: 2}, + {ConsumerName: consumer2, MessageCount: 3}, + }, + } + + // 2. Act + summaryResult, err := client.XPending(key, groupName) + + // 3a. Assert that we get 5 messages in total, 2 for consumer1 and 3 for consumer2 + assert.NoError(suite.T(), err) + assert.True( + suite.T(), + reflect.DeepEqual(expectedSummary, summaryResult), + "Expected and actual results do not match", + ) + + // 3b. Assert that we get 2 details for consumer1 that includes + detailResult, _ := client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetConsumer(consumer1), + ) + assert.Equal(suite.T(), len(detailResult), 2) + assert.Equal(suite.T(), streamid_1.Value(), detailResult[0].Id) + assert.Equal(suite.T(), streamid_2.Value(), detailResult[1].Id) + } + + execCluster := func(client api.GlideClusterClient) { + // 1. Arrange the data + key := uuid.New().String() + groupName := "group" + uuid.New().String() + zeroStreamId := "0" + consumer1 := "consumer-1-" + uuid.New().String() + consumer2 := "consumer-2-" + uuid.New().String() + + command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + + resp, err := client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", resp.Value().(string)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.Value().(bool)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer2} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.Value().(bool)) + + streamid_1, err := client.XAdd(key, [][]string{{"field1", "value1"}}) + assert.NoError(suite.T(), err) + streamid_2, err := client.XAdd(key, [][]string{{"field2", "value2"}}) + assert.NoError(suite.T(), err) + + command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + _, err = client.XAdd(key, [][]string{{"field3", "value3"}}) + assert.NoError(suite.T(), err) + _, err = client.XAdd(key, [][]string{{"field4", "value4"}}) + assert.NoError(suite.T(), err) + streamid_5, err := client.XAdd(key, [][]string{{"field5", "value5"}}) + assert.NoError(suite.T(), err) + + command = []string{"XReadGroup", "GROUP", groupName, consumer2, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + expectedSummary := api.XPendingSummary{ + NumOfMessages: 5, + StartId: streamid_1, + EndId: streamid_5, + ConsumerMessages: []api.ConsumerPendingMessage{ + {ConsumerName: consumer1, MessageCount: 2}, + {ConsumerName: consumer2, MessageCount: 3}, + }, + } + + // 2. Act + summaryResult, err := client.XPending(key, groupName) + + // 3a. Assert that we get 5 messages in total, 2 for consumer1 and 3 for consumer2 + assert.NoError(suite.T(), err) + assert.True( + suite.T(), + reflect.DeepEqual(expectedSummary, summaryResult), + "Expected and actual results do not match", + ) + + // 3b. Assert that we get 2 details for consumer1 that includes + detailResult, _ := client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetConsumer(consumer1), + ) + assert.Equal(suite.T(), len(detailResult), 2) + assert.Equal(suite.T(), streamid_1.Value(), detailResult[0].Id) + assert.Equal(suite.T(), streamid_2.Value(), detailResult[1].Id) + + // + } + + assert.Equal(suite.T(), "OK", "OK") + + // create group and consumer for the group + // this is only needed in order to be able to use custom commands. + // Once the native commands are added, this logic will be refactored. + switch c := client.(type) { + case api.GlideClient: + execStandalone(c) + case api.GlideClusterClient: + execCluster(c) + } + }) +} + +func (suite *GlideTestSuite) TestXPendingFailures() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // TODO: Update tests when XGroupCreate, XGroupCreateConsumer, XReadGroup, XClaim, XClaimJustId and XAck are added to + // the Go client. + // + // This test splits out the cluster and standalone tests into their own functions because we are forced to use + // CustomCommands for many stream commands which are not included in the preview Go client. Using a type switch for + // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be + // collapsed once the native commands are added in a subsequent release. + + execStandalone := func(client api.GlideClient) { + // 1. Arrange the data + key := uuid.New().String() + missingKey := uuid.New().String() + nonStreamKey := uuid.New().String() + groupName := "group" + uuid.New().String() + zeroStreamId := "0" + consumer1 := "consumer-1-" + uuid.New().String() + invalidConsumer := "invalid-consumer-" + uuid.New().String() + + command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + + resp, err := client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", resp.(string)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.(bool)) + + _, err = client.XAdd(key, [][]string{{"field1", "value1"}}) + assert.NoError(suite.T(), err) + _, err = client.XAdd(key, [][]string{{"field2", "value2"}}) + assert.NoError(suite.T(), err) + + // no pending messages yet... + summaryResult, err := client.XPending(key, groupName) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), summaryResult.NumOfMessages) + + detailResult, err := client.XPendingWithOptions(key, groupName, options.NewXPendingOptions("-", "+", 10)) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // read the entire stream for the consumer and mark messages as pending + command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + // sanity check - expect some results: + summaryResult, err = client.XPending(key, groupName) + assert.NoError(suite.T(), err) + assert.True(suite.T(), summaryResult.NumOfMessages > 0) + + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 1).SetConsumer(consumer1), + ) + assert.NoError(suite.T(), err) + assert.True(suite.T(), len(detailResult) > 0) + + // returns empty if + before - + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("+", "-", 10).SetConsumer(consumer1), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // min idletime of 100 seconds shouldn't produce any results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetMinIdleTime(100000), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // invalid consumer - no results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetConsumer(invalidConsumer), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // Return an error when range bound is not a valid ID + _, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("invalid-id", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + + _, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "invalid-id", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + + // invalid count should return no results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", -1), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // Return an error when an invalid group is provided + _, err = client.XPending( + key, + "invalid-group", + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + // non-existent key throws a RequestError (NOGROUP) + _, err = client.XPending( + missingKey, + groupName, + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + _, err = client.XPendingWithOptions( + missingKey, + groupName, + options.NewXPendingOptions("-", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + // Key exists, but it is not a stream + _, _ = client.Set(nonStreamKey, "bar") + _, err = client.XPending( + nonStreamKey, + groupName, + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "WRONGTYPE")) + + _, err = client.XPendingWithOptions( + nonStreamKey, + groupName, + options.NewXPendingOptions("-", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "WRONGTYPE")) + } + + execCluster := func(client api.GlideClusterClient) { + // 1. Arrange the data + key := uuid.New().String() + missingKey := uuid.New().String() + nonStreamKey := uuid.New().String() + groupName := "group" + uuid.New().String() + zeroStreamId := "0" + consumer1 := "consumer-1-" + uuid.New().String() + invalidConsumer := "invalid-consumer-" + uuid.New().String() + + command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + + resp, err := client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", resp.Value().(string)) + + command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} + resp, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + assert.True(suite.T(), resp.Value().(bool)) + + _, err = client.XAdd(key, [][]string{{"field1", "value1"}}) + assert.NoError(suite.T(), err) + _, err = client.XAdd(key, [][]string{{"field2", "value2"}}) + assert.NoError(suite.T(), err) + + // no pending messages yet... + summaryResult, err := client.XPending(key, groupName) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), summaryResult.NumOfMessages) + + detailResult, err := client.XPendingWithOptions(key, groupName, options.NewXPendingOptions("-", "+", 10)) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // read the entire stream for the consumer and mark messages as pending + command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} + _, err = client.CustomCommand(command) + assert.NoError(suite.T(), err) + + // sanity check - expect some results: + summaryResult, err = client.XPending(key, groupName) + assert.NoError(suite.T(), err) + assert.True(suite.T(), summaryResult.NumOfMessages > 0) + + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 1).SetConsumer(consumer1), + ) + assert.NoError(suite.T(), err) + assert.True(suite.T(), len(detailResult) > 0) + + // returns empty if + before - + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("+", "-", 10).SetConsumer(consumer1), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // min idletime of 100 seconds shouldn't produce any results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetMinIdleTime(100000), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // invalid consumer - no results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", 10).SetConsumer(invalidConsumer), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // Return an error when range bound is not a valid ID + _, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("invalid-id", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + + _, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "invalid-id", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + + // invalid count should return no results + detailResult, err = client.XPendingWithOptions( + key, + groupName, + options.NewXPendingOptions("-", "+", -1), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(detailResult)) + + // Return an error when an invalid group is provided + _, err = client.XPending( + key, + "invalid-group", + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + // non-existent key throws a RequestError (NOGROUP) + _, err = client.XPending( + missingKey, + groupName, + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + _, err = client.XPendingWithOptions( + missingKey, + groupName, + options.NewXPendingOptions("-", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + // Key exists, but it is not a stream + _, _ = client.Set(nonStreamKey, "bar") + _, err = client.XPending( + nonStreamKey, + groupName, + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "WRONGTYPE")) + + _, err = client.XPendingWithOptions( + nonStreamKey, + groupName, + options.NewXPendingOptions("-", "+", 10), + ) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "WRONGTYPE")) + } + + assert.Equal(suite.T(), "OK", "OK") + + // create group and consumer for the group + // this is only needed in order to be able to use custom commands. + // Once the native commands are added, this logic will be refactored. + switch c := client.(type) { + case api.GlideClient: + execStandalone(c) + case api.GlideClusterClient: + execCluster(c) + } + }) +} From d8ae33dd4dd9e0893ed818fee3f23cb653f76cd9 Mon Sep 17 00:00:00 2001 From: Edric Cuartero Date: Fri, 17 Jan 2025 11:39:44 +0800 Subject: [PATCH 03/21] GO Implement Dump, Restore and ObjectEncoding Command (#2781) * GO Implement Dump and ObjectEncoding command Signed-off-by: EdricCua Signed-off-by: Edward Liang --- go/api/base_client.go | 37 +++++++++ go/api/command_options.go | 77 +++++++++++++++++++ go/api/generic_base_commands.go | 85 +++++++++++++++++++++ go/integTest/shared_commands_test.go | 108 +++++++++++++++++++++++++++ 4 files changed, 307 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index ffda2f08f1..6505c8700f 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -4947,3 +4947,40 @@ func (client *baseClient) XPendingWithOptions( } return handleXPendingDetailResponse(result) } + +func (client *baseClient) Restore(key string, ttl int64, value string) (Result[string], error) { + return client.RestoreWithOptions(key, ttl, value, NewRestoreOptionsBuilder()) +} + +func (client *baseClient) RestoreWithOptions(key string, ttl int64, + value string, options *RestoreOptions, +) (Result[string], error) { + optionArgs, err := options.toArgs() + if err != nil { + return CreateNilStringResult(), err + } + result, err := client.executeCommand(C.Restore, append([]string{ + key, + utils.IntToString(ttl), value, + }, optionArgs...)) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} + +func (client *baseClient) Dump(key string) (Result[string], error) { + result, err := client.executeCommand(C.Dump, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} + +func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { + result, err := client.executeCommand(C.ObjectEncoding, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} diff --git a/go/api/command_options.go b/go/api/command_options.go index f77902ca6c..dcf17446bc 100644 --- a/go/api/command_options.go +++ b/go/api/command_options.go @@ -278,3 +278,80 @@ func (listDirection ListDirection) toString() (string, error) { return "", &RequestError{"Invalid list direction"} } } + +// Optional arguments to Restore(key string, ttl int64, value string, option *RestoreOptions) +// +// Note IDLETIME and FREQ modifiers cannot be set at the same time. +// +// [valkey.io]: https://valkey.io/commands/restore/ +type RestoreOptions struct { + // Subcommand string to replace existing key. + replace string + // Subcommand string to represent absolute timestamp (in milliseconds) for TTL. + absTTL string + // It represents the idletime/frequency of object. + eviction Eviction +} + +func NewRestoreOptionsBuilder() *RestoreOptions { + return &RestoreOptions{} +} + +const ( + // Subcommand string to replace existing key. + Replace_keyword = "REPLACE" + + // Subcommand string to represent absolute timestamp (in milliseconds) for TTL. + ABSTTL_keyword string = "ABSTTL" +) + +// Custom setter methods to replace existing key. +func (restoreOption *RestoreOptions) SetReplace() *RestoreOptions { + restoreOption.replace = Replace_keyword + return restoreOption +} + +// Custom setter methods to represent absolute timestamp (in milliseconds) for TTL. +func (restoreOption *RestoreOptions) SetABSTTL() *RestoreOptions { + restoreOption.absTTL = ABSTTL_keyword + return restoreOption +} + +// For eviction purpose, you may use IDLETIME or FREQ modifiers. +type Eviction struct { + // It represent IDLETIME or FREQ. + Type EvictionType + // It represents count(int) of the idletime/frequency of object. + Count int64 +} + +type EvictionType string + +const ( + // It represents the idletime of object + IDLETIME EvictionType = "IDLETIME" + // It represents the frequency of object + FREQ EvictionType = "FREQ" +) + +// Custom setter methods set the idletime/frequency of object. +func (restoreOption *RestoreOptions) SetEviction(evictionType EvictionType, count int64) *RestoreOptions { + restoreOption.eviction.Type = evictionType + restoreOption.eviction.Count = count + return restoreOption +} + +func (opts *RestoreOptions) toArgs() ([]string, error) { + args := []string{} + var err error + if opts.replace != "" { + args = append(args, string(opts.replace)) + } + if opts.absTTL != "" { + args = append(args, string(opts.absTTL)) + } + if (opts.eviction != Eviction{}) { + args = append(args, string(opts.eviction.Type), utils.IntToString(opts.eviction.Count)) + } + return args, err +} diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index 92e88ced65..7897c69e19 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -48,4 +48,89 @@ type GenericBaseCommands interface { Renamenx(key string, newKey string) (bool, error) Persist(key string) (bool, error) + + // Create a key associated with a value that is obtained by + // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). + // + // Parameters: + // key - The key to create. + // ttl - The expiry time (in milliseconds). If 0, the key will persist. + // value - The serialized value to deserialize and assign to key. + // + // Return value: + // Return OK if successfully create a key with a value . + // + // Example: + // result, err := client.Restore("key",ttl, value) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: OK + // + // [valkey.io]: https://valkey.io/commands/restore/ + Restore(key string, ttl int64, value string) (Result[string], error) + + // Create a key associated with a value that is obtained by + // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). + // + // Parameters: + // key - The key to create. + // ttl - The expiry time (in milliseconds). If 0, the key will persist. + // value - The serialized value to deserialize and assign to key. + // restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency + // + // Return value: + // Return OK if successfully create a key with a value. + // + // Example: + // restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) + // resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: OK + // + // [valkey.io]: https://valkey.io/commands/restore/ + RestoreWithOptions(key string, ttl int64, value string, option *RestoreOptions) (Result[string], error) + + // Returns the internal encoding for the Valkey object stored at key. + // + // Note: + // When in cluster mode, both key and newkey must map to the same hash slot. + // + // Parameters: + // The key of the object to get the internal encoding of. + // + // Return value: + // If key exists, returns the internal encoding of the object stored at + // key as a String. Otherwise, returns null. + // + // Example: + // result, err := client.ObjectEncoding("mykeyRenamenx") + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: embstr + // + // [valkey.io]: https://valkey.io/commands/object-encoding/ + ObjectEncoding(key string) (Result[string], error) + + // Serialize the value stored at key in a Valkey-specific format and return it to the user. + // + // Parameters: + // The key to serialize. + // + // Return value: + // The serialized value of the data stored at key + // If key does not exist, null will be returned. + // + // Example: + // result, err := client.Dump([]string{"key"}) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: (Serialized Value) + // + // [valkey.io]: https://valkey.io/commands/dump/ + Dump(key string) (Result[string], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index be729bdad4..5a01b8595b 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5716,3 +5716,111 @@ func (suite *GlideTestSuite) TestXPendingFailures() { } }) } + +func (suite *GlideTestSuite) TestObjectEncoding() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // Test 1: Check object encoding for embstr + key := "{keyName}" + uuid.NewString() + value1 := "Hello" + t := suite.T() + suite.verifyOK(client.Set(key, value1)) + resultObjectEncoding, err := client.ObjectEncoding(key) + assert.Nil(t, err) + assert.Equal(t, "embstr", resultObjectEncoding.Value(), "The result should be embstr") + + // Test 2: Check object encoding command for non existing key + key2 := "{keyName}" + uuid.NewString() + resultDumpNull, err := client.ObjectEncoding(key2) + assert.Nil(t, err) + assert.Equal(t, "", resultDumpNull.Value()) + }) +} + +func (suite *GlideTestSuite) TestDumpRestore() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // Test 1: Check restore command for deleted key and check value + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(client.Set(key, value)) + resultDump, err := client.Dump(key) + assert.Nil(t, err) + assert.NotNil(t, resultDump) + deletedCount, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), deletedCount) + result_test1, err := client.Restore(key, int64(0), resultDump.Value()) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test1.Value()) + resultGetRestoreKey, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGetRestoreKey.Value()) + + // Test 2: Check dump command for non existing key + key1 := "{keyName}" + uuid.NewString() + resultDumpNull, err := client.Dump(key1) + assert.Nil(t, err) + assert.Equal(t, "", resultDumpNull.Value()) + }) +} + +func (suite *GlideTestSuite) TestRestoreWithOptions() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(client.Set(key, value)) + + resultDump, err := client.Dump(key) + assert.Nil(t, err) + assert.NotNil(t, resultDump) + + // Test 1: Check restore command with restoreOptions REPLACE modifier + deletedCount, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), deletedCount) + optsReplace := api.NewRestoreOptionsBuilder().SetReplace() + result_test1, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), optsReplace) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test1.Value()) + resultGetRestoreKey, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGetRestoreKey.Value()) + + // Test 2: Check restore command with restoreOptions ABSTTL modifier + delete_test2, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test2) + opts_test2 := api.NewRestoreOptionsBuilder().SetABSTTL() + result_test2, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test2.Value()) + resultGet_test2, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test2.Value()) + + // Test 3: Check restore command with restoreOptions FREQ modifier + delete_test3, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test3) + opts_test3 := api.NewRestoreOptionsBuilder().SetEviction(api.FREQ, 10) + result_test3, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test3) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test3.Value()) + resultGet_test3, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test3.Value()) + + // Test 4: Check restore command with restoreOptions IDLETIME modifier + delete_test4, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test4) + opts_test4 := api.NewRestoreOptionsBuilder().SetEviction(api.IDLETIME, 10) + result_test4, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test4) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test4.Value()) + resultGet_test4, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test4.Value()) + }) +} From 3b7ddc3c95acd76be3430f11d5b462aaa926758c Mon Sep 17 00:00:00 2001 From: Edric Cuartero Date: Sat, 18 Jan 2025 01:57:17 +0800 Subject: [PATCH 04/21] GO: Implement Echo Command (#2863) * Implement Echo Command Signed-off-by: EdricCua Signed-off-by: Edward Liang --- go/api/base_client.go | 28 ++++++++++++++++++++++++ go/api/connection_management_commands.go | 19 ++++++++++++++++ go/integTest/shared_commands_test.go | 11 ++++++++++ 3 files changed, 58 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index 6505c8700f..604a67c79f 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -4984,3 +4984,31 @@ func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { } return handleStringOrNilResponse(result) } + +// Echo the provided message back. +// The command will be routed a random node. +// +// Parameters: +// +// message - The provided message. +// +// Return value: +// +// The provided message +// +// For example: +// +// result, err := client.Echo("Hello World") +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: Hello World +// +// [valkey.io]: https://valkey.io/commands/echo/ +func (client *baseClient) Echo(message string) (Result[string], error) { + result, err := client.executeCommand(C.Echo, []string{message}) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} diff --git a/go/api/connection_management_commands.go b/go/api/connection_management_commands.go index f51f41cf29..59d4c3c372 100644 --- a/go/api/connection_management_commands.go +++ b/go/api/connection_management_commands.go @@ -12,4 +12,23 @@ type ConnectionManagementCommands interface { Ping() (string, error) PingWithMessage(message string) (string, error) + + // Echo the provided message back. + // The command will be routed a random node. + // + // Parameters: + // message - The provided message. + // + // Return value: + // The provided message + // + // For example: + // result, err := client.Echo("Hello World") + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: Hello World + // + // [valkey.io]: https://valkey.io/commands/echo/ + Echo(message string) (Result[string], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 5a01b8595b..333be34c72 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5824,3 +5824,14 @@ func (suite *GlideTestSuite) TestRestoreWithOptions() { assert.Equal(t, value, resultGet_test4.Value()) }) } + +func (suite *GlideTestSuite) TestEcho() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // Test 1: Check if Echo command return the message + value := "Hello world" + t := suite.T() + resultEcho, err := client.Echo(value) + assert.Nil(t, err) + assert.Equal(t, value, resultEcho.Value()) + }) +} From 11acb5d3d7fa8997b39b6eae2b7c433d7398beca Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Fri, 17 Jan 2025 10:23:13 -0800 Subject: [PATCH 05/21] Go: `XREADGROUP`. (#2949) * Go: `XREADGROUP`. Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 246 +++++++++++++++++++++++++++ go/api/options/stream_options.go | 45 +++++ go/api/response_handlers.go | 82 ++++++++- go/api/stream_commands.go | 9 + go/integTest/shared_commands_test.go | 141 +++++++++++++++ 5 files changed, 516 insertions(+), 7 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 604a67c79f..b99b3192d2 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -3908,6 +3908,252 @@ func (client *Command) XReadWithOptions( return handleXReadResponse(result) } +// Reads entries from the given streams owned by a consumer group. +// +// Note: +// +// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// group - The consumer group name. +// consumer - The group consumer. +// keysAndIds - A map of keys and entry IDs to read from. +// +// 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. +// +// For example: +// +// result, err := client.XReadGroup({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}) +// err == nil: true +// result: map[string]map[string][][]string{ +// "stream1": { +// "0-1": {{"field1", "value1"}}, +// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, +// }, +// "stream2": { +// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, +// "1526985685298-0": nil, // entry was deleted +// }, +// "stream3": {}, // stream is empty +// } +// +// [valkey.io]: https://valkey.io/commands/xreadgroup/ +func (client *baseClient) XReadGroup( + group string, + consumer string, + keysAndIds map[string]string, +) (map[string]map[string][][]string, error) { + return client.XReadGroupWithOptions(group, consumer, keysAndIds, options.NewXReadGroupOptions()) +} + +// Reads entries from the given streams owned by a consumer group. +// +// Note: +// +// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// group - The consumer group name. +// consumer - The group consumer. +// keysAndIds - A map of keys and entry IDs to read from. +// options - Options detailing how to read the stream. +// +// 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. +// +// For example: +// +// options := options.NewXReadGroupOptions().SetNoAck() +// result, err := client.XReadGroupWithOptions({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}, options) +// err == nil: true +// result: map[string]map[string][][]string{ +// "stream1": { +// "0-1": {{"field1", "value1"}}, +// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, +// }, +// "stream2": { +// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, +// "1526985685298-0": nil, // entry was deleted +// }, +// "stream3": {}, // stream is empty +// } +// +// [valkey.io]: https://valkey.io/commands/xreadgroup/ +func (client *baseClient) XReadGroupWithOptions( + group string, + consumer string, + keysAndIds map[string]string, + options *options.XReadGroupOptions, +) (map[string]map[string][][]string, error) { + args, err := createStreamCommandArgs([]string{"GROUP", group, consumer}, keysAndIds, options) + if err != nil { + return nil, err + } + + result, err := client.executeCommand(C.XReadGroup, args) + if err != nil { + return nil, err + } + + return handleXReadGroupResponse(result) +} + +// Combine `args` with `keysAndIds` and `options` into arguments for a stream command +func createStreamCommandArgs( + args []string, + keysAndIds map[string]string, + options interface{ ToArgs() ([]string, error) }, +) ([]string, error) { + optionArgs, err := options.ToArgs() + if err != nil { + return nil, err + } + args = append(args, optionArgs...) + // Note: this loop iterates in an indeterminate order, but it is OK for that case + keys := make([]string, 0, len(keysAndIds)) + values := make([]string, 0, len(keysAndIds)) + for key := range keysAndIds { + keys = append(keys, key) + values = append(values, keysAndIds[key]) + } + args = append(args, "STREAMS") + args = append(args, keys...) + args = append(args, values...) + return args, nil +} + +// Reads entries from the given streams owned by a consumer group. +// +// Note: +// +// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// group - The consumer group name. +// consumer - The group consumer. +// keysAndIds - A map of keys and entry IDs to read from. +// +// 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. +// +// For example: +// +// result, err := client.XReadGroup({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}) +// err == nil: true +// result: map[string]map[string][][]string{ +// "stream1": { +// "0-1": {{"field1", "value1"}}, +// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, +// }, +// "stream2": { +// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, +// "1526985685298-0": nil, // entry was deleted +// }, +// "stream3": {}, // stream is empty +// } +// +// [valkey.io]: https://valkey.io/commands/xreadgroup/ +func (client *baseClient) XReadGroup( + group string, + consumer string, + keysAndIds map[string]string, +) (map[string]map[string][][]string, error) { + return client.XReadGroupWithOptions(group, consumer, keysAndIds, options.NewXReadGroupOptions()) +} + +// Reads entries from the given streams owned by a consumer group. +// +// Note: +// +// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. +// +// See [valkey.io] for details. +// +// Parameters: +// +// group - The consumer group name. +// consumer - The group consumer. +// keysAndIds - A map of keys and entry IDs to read from. +// options - Options detailing how to read the stream. +// +// 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. +// +// For example: +// +// options := options.NewXReadGroupOptions().SetNoAck() +// result, err := client.XReadGroupWithOptions({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}, options) +// err == nil: true +// result: map[string]map[string][][]string{ +// "stream1": { +// "0-1": {{"field1", "value1"}}, +// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, +// }, +// "stream2": { +// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, +// "1526985685298-0": nil, // entry was deleted +// }, +// "stream3": {}, // stream is empty +// } +// +// [valkey.io]: https://valkey.io/commands/xreadgroup/ +func (client *baseClient) XReadGroupWithOptions( + group string, + consumer string, + keysAndIds map[string]string, + options *options.XReadGroupOptions, +) (map[string]map[string][][]string, error) { + args, err := createStreamCommandArgs([]string{"GROUP", group, consumer}, keysAndIds, options) + if err != nil { + return nil, err + } + + result, err := client.executeCommand(C.XReadGroup, args) + if err != nil { + return nil, err + } + + return handleXReadGroupResponse(result) +} + +// Combine `args` with `keysAndIds` and `options` into arguments for a stream command +func createStreamCommandArgs( + args []string, + keysAndIds map[string]string, + options interface{ ToArgs() ([]string, error) }, +) ([]string, error) { + optionArgs, err := options.ToArgs() + if err != nil { + return nil, err + } + args = append(args, optionArgs...) + // Note: this loop iterates in an indeterminate order, but it is OK for that case + keys := make([]string, 0, len(keysAndIds)) + values := make([]string, 0, len(keysAndIds)) + for key := range keysAndIds { + keys = append(keys, key) + values = append(values, keysAndIds[key]) + } + args = append(args, "STREAMS") + args = append(args, keys...) + args = append(args, values...) + return args, nil +} + // Adds one or more members to a sorted set, or updates their scores. Creates the key if it doesn't exist. // // See [valkey.io] for details. diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index 19f5e6d5c4..ff40c224ac 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -150,6 +150,51 @@ func (xro *XReadOptions) ToArgs() ([]string, error) { return args, nil } +// Optional arguments for `XReadGroup` in [StreamCommands] +type XReadGroupOptions struct { + count, block int64 + noAck bool +} + +// Create new empty `XReadOptions` +func NewXReadGroupOptions() *XReadGroupOptions { + return &XReadGroupOptions{-1, -1, false} +} + +// The maximal number of elements requested. Equivalent to `COUNT` in the Valkey API. +func (xrgo *XReadGroupOptions) SetCount(count int64) *XReadGroupOptions { + xrgo.count = count + return xrgo +} + +// If set, the request will be blocked for the set amount of milliseconds or until the server has +// the required number of entries. A value of `0` will block indefinitely. Equivalent to `BLOCK` in the Valkey API. +func (xrgo *XReadGroupOptions) SetBlock(block int64) *XReadGroupOptions { + xrgo.block = block + return xrgo +} + +// If set, messages are not added to the Pending Entries List (PEL). This is equivalent to +// acknowledging the message when it is read. +func (xrgo *XReadGroupOptions) SetNoAck() *XReadGroupOptions { + xrgo.noAck = true + return xrgo +} + +func (xrgo *XReadGroupOptions) ToArgs() ([]string, error) { + args := []string{} + if xrgo.count >= 0 { + args = append(args, "COUNT", utils.IntToString(xrgo.count)) + } + if xrgo.block >= 0 { + args = append(args, "BLOCK", utils.IntToString(xrgo.block)) + } + if xrgo.noAck { + args = append(args, "NOACK") + } + return args, nil +} + // Optional arguments for `XPending` in [StreamCommands] type XPendingOptions struct { minIdleTime int64 diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 07ab7e1a09..c848cdc57e 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -513,17 +513,25 @@ type responseConverter interface { // convert maps, T - type of the value, key is string type mapConverter[T any] struct { - next responseConverter + next responseConverter + canBeNil bool } func (node mapConverter[T]) convert(data interface{}) (interface{}, error) { + if data == nil { + if node.canBeNil { + return nil, nil + } else { + return nil, &RequestError{fmt.Sprintf("Unexpected type received: nil, expected: map[string]%v", getType[T]())} + } + } result := make(map[string]T) for key, value := range data.(map[string]interface{}) { if node.next == nil { valueT, ok := value.(T) if !ok { - return nil, &RequestError{fmt.Sprintf("Unexpected type received: %T, expected: %v", value, getType[T]())} + return nil, &RequestError{fmt.Sprintf("Unexpected type of map element: %T, expected: %v", value, getType[T]())} } result[key] = valueT } else { @@ -531,9 +539,14 @@ func (node mapConverter[T]) convert(data interface{}) (interface{}, error) { if err != nil { return nil, err } + if val == nil { + var null T + result[key] = null + continue + } valueT, ok := val.(T) if !ok { - return nil, &RequestError{fmt.Sprintf("Unexpected type received: %T, expected: %v", valueT, getType[T]())} + return nil, &RequestError{fmt.Sprintf("Unexpected type of map element: %T, expected: %v", val, getType[T]())} } result[key] = valueT } @@ -544,17 +557,27 @@ func (node mapConverter[T]) convert(data interface{}) (interface{}, error) { // convert arrays, T - type of the value type arrayConverter[T any] struct { - next responseConverter + next responseConverter + canBeNil bool } func (node arrayConverter[T]) convert(data interface{}) (interface{}, error) { + if data == nil { + if node.canBeNil { + return nil, nil + } else { + return nil, &RequestError{fmt.Sprintf("Unexpected type received: nil, expected: []%v", getType[T]())} + } + } arrData := data.([]interface{}) result := make([]T, 0, len(arrData)) for _, value := range arrData { if node.next == nil { valueT, ok := value.(T) if !ok { - return nil, &RequestError{fmt.Sprintf("Unexpected type received: %T, expected: %v", value, getType[T]())} + return nil, &RequestError{ + fmt.Sprintf("Unexpected type of array element: %T, expected: %v", value, getType[T]()), + } } result = append(result, valueT) } else { @@ -562,9 +585,14 @@ func (node arrayConverter[T]) convert(data interface{}) (interface{}, error) { if err != nil { return nil, err } + if val == nil { + var null T + result = append(result, null) + continue + } valueT, ok := val.(T) if !ok { - return nil, &RequestError{fmt.Sprintf("Unexpected type received: %T, expected: %v", valueT, getType[T]())} + return nil, &RequestError{fmt.Sprintf("Unexpected type of array element: %T, expected: %v", val, getType[T]())} } result = append(result, valueT) } @@ -588,9 +616,49 @@ func handleXReadResponse(response *C.struct_CommandResponse) (map[string]map[str converters := mapConverter[map[string][][]string]{ mapConverter[[][]string]{ arrayConverter[[]string]{ - arrayConverter[string]{}, + arrayConverter[string]{ + nil, + false, + }, + false, + }, + false, + }, + false, + } + + res, err := converters.convert(data) + if err != nil { + return nil, err + } + if result, ok := res.(map[string]map[string][][]string); ok { + return result, nil + } + return nil, &RequestError{fmt.Sprintf("unexpected type received: %T", res)} +} + +func handleXReadGroupResponse(response *C.struct_CommandResponse) (map[string]map[string][][]string, error) { + defer C.free_command_response(response) + data, err := parseMap(response) + if err != nil { + return nil, err + } + if data == nil { + return nil, nil + } + + converters := mapConverter[map[string][][]string]{ + mapConverter[[][]string]{ + arrayConverter[[]string]{ + arrayConverter[string]{ + nil, + false, + }, + true, }, + false, }, + false, } res, err := converters.convert(data) diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 0e20543902..46ddd092fc 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -19,6 +19,15 @@ type StreamCommands interface { XLen(key string) (int64, error) + XReadGroup(group string, consumer string, keysAndIds map[string]string) (map[string]map[string][][]string, error) + + XReadGroupWithOptions( + group string, + consumer string, + keysAndIds map[string]string, + options *options.XReadGroupOptions, + ) (map[string]map[string][][]string, error) + XRead(keysAndIds map[string]string) (map[string]map[string][][]string, error) XReadWithOptions(keysAndIds map[string]string, options *options.XReadOptions) (map[string]map[string][][]string, error) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 333be34c72..6f3d019872 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4105,6 +4105,147 @@ func (suite *GlideTestSuite) TestXAddWithOptions() { }) } +// submit args with custom command API, check that no error returned. +// returns a response or raises `errMsg` if failed to submit the command. +func sendWithCustomCommand(suite *GlideTestSuite, client api.BaseClient, args []string, errMsg string) any { + var res any + var err error + switch c := client.(type) { + case api.GlideClient: + res, err = c.CustomCommand(args) + case api.GlideClusterClient: + res, err = c.CustomCommand(args) + default: + suite.FailNow(errMsg) + } + assert.NoError(suite.T(), err) + return res +} + +func (suite *GlideTestSuite) TestXReadGroup() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := "{xreadgroup}-1-" + uuid.NewString() + key2 := "{xreadgroup}-2-" + uuid.NewString() + key3 := "{xreadgroup}-3-" + uuid.NewString() + group := uuid.NewString() + consumer := uuid.NewString() + + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key1, group, "0", "MKSTREAM"}, + "Can't send XGROUP CREATE as a custom command", + ) + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "createconsumer", key1, group, consumer}, + "Can't send XGROUP CREATECONSUMER as a custom command", + ) + + entry1, err := client.XAdd(key1, [][]string{{"a", "b"}}) + assert.NoError(suite.T(), err) + assert.False(suite.T(), entry1.IsNil()) + entry2, err := client.XAdd(key1, [][]string{{"c", "d"}}) + assert.NoError(suite.T(), err) + assert.False(suite.T(), entry2.IsNil()) + + // read the entire stream for the consumer and mark messages as pending + res, err := client.XReadGroup(group, consumer, map[string]string{key1: ">"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key1: { + entry1.Value(): {{"a", "b"}}, + entry2.Value(): {{"c", "d"}}, + }, + }, res) + + // delete one of the entries + sendWithCustomCommand(suite, client, []string{"xdel", key1, entry1.Value()}, "Can't send XDEL as a custom command") + + // now xreadgroup returns one empty entry and one non-empty entry + res, err = client.XReadGroup(group, consumer, map[string]string{key1: "0"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key1: { + entry1.Value(): nil, + entry2.Value(): {{"c", "d"}}, + }, + }, res) + + // try to read new messages only + res, err = client.XReadGroup(group, consumer, map[string]string{key1: ">"}) + assert.NoError(suite.T(), err) + assert.Nil(suite.T(), res) + + // add a message and read it with ">" + entry3, err := client.XAdd(key1, [][]string{{"e", "f"}}) + assert.NoError(suite.T(), err) + assert.False(suite.T(), entry3.IsNil()) + res, err = client.XReadGroup(group, consumer, map[string]string{key1: ">"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key1: { + entry3.Value(): {{"e", "f"}}, + }, + }, res) + + // add second key with a group and a consumer, but no messages + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key2, group, "0", "MKSTREAM"}, + "Can't send XGROUP CREATE as a custom command", + ) + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "createconsumer", key2, group, consumer}, + "Can't send XGROUP CREATECONSUMER as a custom command", + ) + + // read both keys + res, err = client.XReadGroup(group, consumer, map[string]string{key1: "0", key2: "0"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key1: { + entry1.Value(): nil, + entry2.Value(): {{"c", "d"}}, + entry3.Value(): {{"e", "f"}}, + }, + key2: {}, + }, res) + + // error cases: + // key does not exist + _, err = client.XReadGroup("_", "_", map[string]string{key3: "0"}) + assert.IsType(suite.T(), &api.RequestError{}, err) + // key is not a stream + suite.verifyOK(client.Set(key3, uuid.New().String())) + _, err = client.XReadGroup("_", "_", map[string]string{key3: "0"}) + assert.IsType(suite.T(), &api.RequestError{}, err) + del, err := client.Del([]string{key3}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), del) + // group and consumer don't exist + xadd, err := client.XAdd(key3, [][]string{{"a", "b"}}) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), xadd) + _, err = client.XReadGroup("_", "_", map[string]string{key3: "0"}) + assert.IsType(suite.T(), &api.RequestError{}, err) + // consumer don't exist + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key3, group, "0-0"}, + "Can't send XGROUP CREATE as a custom command", + ) + res, err = client.XReadGroup(group, "_", map[string]string{key3: "0"}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{key3: {}}, res) + }) +} + func (suite *GlideTestSuite) TestXRead() { suite.runWithDefaultClients(func(client api.BaseClient) { key1 := "{xread}" + uuid.NewString() From a5fbce534430890c19ee4cb3157c4a7980923ebd Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Fri, 17 Jan 2025 13:32:31 -0800 Subject: [PATCH 06/21] Java: Shadow protobuf dependency (#2931) * Shadow protobuf Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- .github/workflows/ort.yml | 11 +++++++++ CHANGELOG.md | 1 + java/benchmarks/build.gradle | 11 ++++----- java/build.gradle | 5 ++++- java/client/build.gradle | 43 ++++++++++++++++++++++++++++-------- java/integTest/build.gradle | 6 +++-- 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ort.yml b/.github/workflows/ort.yml index 403560286d..d88f2287fd 100644 --- a/.github/workflows/ort.yml +++ b/.github/workflows/ort.yml @@ -158,6 +158,17 @@ jobs: distribution: "temurin" java-version: 11 + - name: Install protoc (protobuf) + uses: arduino/setup-protoc@v3 + with: + version: "29.1" + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build java artifact + working-directory: ./java + run: | + ./gradlew publishToMavenLocal -x buildRust -x javadoc + - name: Run ORT tools for Java uses: ./.github/workflows/run-ort-tools with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a99a17aa..299180a2a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * Go: Add `ZCARD` ([#2838](https://github.com/valkey-io/valkey-glide/pull/2838)) * Java, Node, Python: Update documentation for CONFIG SET and CONFIG GET ([#2919](https://github.com/valkey-io/valkey-glide/pull/2919)) * Go: Add `BZPopMin` ([#2849](https://github.com/valkey-io/valkey-glide/pull/2849)) +* Java: Shadow `protobuf` dependency ([#2931](https://github.com/valkey-io/valkey-glide/pull/2931)) * Java: Add `RESP2` support ([#2383](https://github.com/valkey-io/valkey-glide/pull/2383)) * Node: Add `IFEQ` option ([#2909](https://github.com/valkey-io/valkey-glide/pull/2909)) diff --git a/java/benchmarks/build.gradle b/java/benchmarks/build.gradle index b4777ee410..f8e62ab6d7 100644 --- a/java/benchmarks/build.gradle +++ b/java/benchmarks/build.gradle @@ -7,16 +7,13 @@ plugins { repositories { // Use Maven Central for resolving dependencies. mavenCentral() + mavenLocal() } dependencies { - def releaseVersion = System.getenv("GLIDE_RELEASE_VERSION"); + version = System.getenv("GLIDE_RELEASE_VERSION") ?: project.ext.defaultReleaseVersion - if (releaseVersion) { - implementation "io.valkey:valkey-glide:" + releaseVersion + ":${osdetector.classifier}" - } else { - implementation project(':client') - } + implementation "io.valkey:valkey-glide:${version}:${osdetector.classifier}" // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:32.1.1-jre' @@ -28,7 +25,7 @@ dependencies { implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' } -run.dependsOn ':client:buildRust' +compileJava.dependsOn ':client:publishToMavenLocal' application { // Define the main class for the application. diff --git a/java/build.gradle b/java/build.gradle index 1db21c0404..31acdb0902 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -41,7 +41,10 @@ subprojects { // finalizedBy jacocoTestReport, jacocoTestCoverageVerification } - ext.failedTests = [] + ext { + defaultReleaseVersion = "255.255.255" + failedTests = [] + } tasks.withType(Test) { afterTest { TestDescriptor descriptor, TestResult result -> diff --git a/java/client/build.gradle b/java/client/build.gradle index 7ae0d7c429..efdbd6b7f8 100644 --- a/java/client/build.gradle +++ b/java/client/build.gradle @@ -7,22 +7,27 @@ plugins { id 'io.freefair.lombok' version '8.6' id 'com.github.spotbugs' version '6.0.18' id 'com.google.osdetector' version '1.7.3' + id 'com.gradleup.shadow' version '8.3.5' } repositories { mavenCentral() } +configurations { + testImplementation { extendsFrom shadow } +} + dependencies { implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '4.29.1' - implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' + shadow group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' - implementation group: 'io.netty', name: 'netty-handler', version: '4.1.115.Final' + shadow group: 'io.netty', name: 'netty-handler', version: '4.1.115.Final' // https://github.com/netty/netty/wiki/Native-transports // At the moment, Windows is not supported - implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.115.Final', classifier: 'linux-x86_64' - implementation group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.115.Final', classifier: 'linux-aarch_64' - implementation group: 'io.netty', name: 'netty-transport-native-kqueue', version: '4.1.115.Final', classifier: 'osx-aarch_64' + shadow group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.115.Final', classifier: 'linux-x86_64' + shadow group: 'io.netty', name: 'netty-transport-native-epoll', version: '4.1.115.Final', classifier: 'linux-aarch_64' + shadow group: 'io.netty', name: 'netty-transport-native-kqueue', version: '4.1.115.Final', classifier: 'osx-aarch_64' // junit testImplementation group: 'org.mockito', name: 'mockito-inline', version: '3.12.4' @@ -130,8 +135,6 @@ tasks.register('copyNativeLib', Copy) { into sourceSets.main.output.resourcesDir } -def defaultReleaseVersion = "255.255.255"; - delombok.dependsOn('compileJava') jar.dependsOn('copyNativeLib') javadoc.dependsOn('copyNativeLib') @@ -155,13 +158,26 @@ sourceSets { } } +task javadocJar(type: Jar, dependsOn: javadoc) { + archiveClassifier = 'javadoc' + from javadoc.destinationDir +} + +task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier = 'sources' + from sourceSets.main.allSource + exclude 'glide/models' // exclude protobuf files +} + publishing { publications { mavenJava(MavenPublication) { - from components.java + from components.shadow + artifact javadocJar + artifact sourcesJar groupId = 'io.valkey' artifactId = 'valkey-glide' - version = System.getenv("GLIDE_RELEASE_VERSION") ?: defaultReleaseVersion; + version = System.getenv("GLIDE_RELEASE_VERSION") ?: project.ext.defaultReleaseVersion pom { name = 'valkey-glide' description = 'General Language Independent Driver for the Enterprise (GLIDE) for Valkey' @@ -193,6 +209,10 @@ publishing { } } +tasks.withType(GenerateModuleMetadata) { + dependsOn jar, shadowJar +} + java { modularity.inferModulePath = true withSourcesJar() @@ -223,6 +243,11 @@ jar { archiveClassifier = osdetector.classifier } +shadowJar { + dependsOn('copyNativeLib') + archiveClassifier = osdetector.classifier +} + sourcesJar { // suppress following error // Entry glide/api/BaseClient.java is a duplicate but no duplicate handling strategy has been set diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index 8ebd7f272e..ebe42ce072 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -1,14 +1,16 @@ plugins { id 'java-library' + id "com.google.osdetector" version "1.7.3" } repositories { mavenCentral() + mavenLocal() } dependencies { // client - implementation project(':client') + implementation group: 'io.valkey', name: 'valkey-glide', version: project.ext.defaultReleaseVersion, classifier: osdetector.classifier implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' implementation 'com.google.code.gson:gson:2.10.1' @@ -129,7 +131,7 @@ clearDirs.finalizedBy 'startStandalone' clearDirs.finalizedBy 'startCluster' clearDirs.finalizedBy 'startClusterForAz' test.finalizedBy 'stopAllAfterTests' -test.dependsOn ':client:buildRust' +compileTestJava.dependsOn ':client:publishToMavenLocal' tasks.withType(Test) { doFirst { From 18aa5f6722a4a142727e7b6a053cc91bd06b4f8b Mon Sep 17 00:00:00 2001 From: tjzhang-BQ <111323543+tjzhang-BQ@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:47:39 -0800 Subject: [PATCH 07/21] Go: Add commands ZRemRangeByRank/ZRemRangeByScore/ZRemRangeByLex (#2967) Signed-off-by: TJ Zhang Co-authored-by: TJ Zhang Signed-off-by: Edward Liang --- go/api/base_client.go | 95 ++++++++++++++++ go/api/options/zrange_options.go | 12 ++ go/api/sorted_set_commands.go | 6 + go/integTest/shared_commands_test.go | 159 +++++++++++++++++++++++++++ 4 files changed, 272 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index b99b3192d2..ebd529c285 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -5258,3 +5258,98 @@ func (client *baseClient) Echo(message string) (Result[string], error) { } return handleStringOrNilResponse(result) } + +// Removes all elements in the sorted set stored at `key` with a lexicographical order +// between `rangeQuery.Start` and `rangeQuery.End`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// 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: +// +// The number of members removed from the sorted set. +// If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. +// If `rangeQuery.Start` is greater than `rangeQuery.End`, `0` is returned. +// +// Example: +// +// zRemRangeByLexResult, err := client.ZRemRangeByLex("key1", options.NewRangeByLexQuery("a", "b")) +// fmt.Println(zRemRangeByLexResult) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/zremrangebylex/ +func (client *baseClient) ZRemRangeByLex(key string, rangeQuery options.RangeByLex) (int64, error) { + result, err := client.executeCommand( + C.ZRemRangeByLex, append([]string{key}, rangeQuery.ToArgsRemRange()...)) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} + +// Removes all elements in the sorted set stored at `key` with a rank between `start` and `stop`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the sorted set. +// start - The start rank. +// stop - The stop rank. +// +// Return value: +// +// The number of members removed from the sorted set. +// If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. +// If `start` is greater than `stop`, `0` is returned. +// +// Example: +// +// zRemRangeByRankResult, err := client.ZRemRangeByRank("key1", 0, 1) +// fmt.Println(zRemRangeByRankResult) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/zremrangebyrank/ +func (client *baseClient) ZRemRangeByRank(key string, start int64, stop int64) (int64, error) { + result, err := client.executeCommand(C.ZRemRangeByRank, []string{key, utils.IntToString(start), utils.IntToString(stop)}) + if err != nil { + return 0, err + } + return handleIntResponse(result) +} + +// Removes all elements in the sorted set stored at `key` with a score between `rangeQuery.Start` and `rangeQuery.End`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the sorted set. +// rangeQuery - The range query object representing the minimum and maximum bound of the score range. +// can be an implementation of [options.RangeByScore]. +// +// Return value: +// +// The number of members removed from the sorted set. +// If `key` does not exist, it is treated as an empty sorted set, and the command returns `0`. +// If `rangeQuery.Start` is greater than `rangeQuery.End`, `0` is returned. +// +// Example: +// +// zRemRangeByScoreResult, err := client.ZRemRangeByScore("key1", options.NewRangeByScoreBuilder( +// options.NewInfiniteScoreBoundary(options.NegativeInfinity), +// options.NewInfiniteScoreBoundary(options.PositiveInfinity), +// )) +// fmt.Println(zRemRangeByScoreResult) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/zremrangebyscore/ +func (client *baseClient) ZRemRangeByScore(key string, rangeQuery options.RangeByScore) (int64, error) { + result, err := client.executeCommand(C.ZRemRangeByScore, append([]string{key}, rangeQuery.ToArgsRemRange()...)) + if err != nil { + return 0, err + } + return handleIntResponse(result) +} diff --git a/go/api/options/zrange_options.go b/go/api/options/zrange_options.go index 002dc38e24..d89e9124b8 100644 --- a/go/api/options/zrange_options.go +++ b/go/api/options/zrange_options.go @@ -14,6 +14,10 @@ type ZRangeQuery interface { ToArgs() []string } +type ZRemRangeQuery interface { + ToArgsRemRange() []string +} + // Queries a range of elements from a sorted set by theirs index. type RangeByIndex struct { start, end int64 @@ -152,6 +156,10 @@ func (rbs *RangeByScore) ToArgs() []string { return args } +func (rbs *RangeByScore) ToArgsRemRange() []string { + return []string{string(rbs.start), string(rbs.end)} +} + // Queries a range of elements from a sorted set by theirs lexicographical order. // // Parameters: @@ -186,6 +194,10 @@ func (rbl *RangeByLex) ToArgs() []string { return args } +func (rbl *RangeByLex) ToArgsRemRange() []string { + return []string{string(rbl.start), string(rbl.end)} +} + // Query for `ZRangeWithScores` in [SortedSetCommands] // - For range queries by index (rank), use `RangeByIndex`. // - For range queries by score, use `RangeByScore`. diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index 7021ca9968..75107163eb 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -56,4 +56,10 @@ type SortedSetCommands interface { ZScan(key string, cursor string) (Result[string], []Result[string], error) ZScanWithOptions(key string, cursor string, options *options.ZScanOptions) (Result[string], []Result[string], error) + + ZRemRangeByLex(key string, rangeQuery options.RangeByLex) (int64, error) + + ZRemRangeByRank(key string, start int64, stop int64) (int64, error) + + ZRemRangeByScore(key string, rangeQuery options.RangeByScore) (int64, error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 6f3d019872..32dcb6a055 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5976,3 +5976,162 @@ func (suite *GlideTestSuite) TestEcho() { assert.Equal(t, value, resultEcho.Value()) }) } + +func (suite *GlideTestSuite) TestZRemRangeByRank() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := uuid.New().String() + stringKey := uuid.New().String() + membersScores := map[string]float64{ + "one": 1.0, + "two": 2.0, + "three": 3.0, + } + zAddResult, err := client.ZAdd(key1, membersScores) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(3), zAddResult) + + // Incorrect range start > stop + zRemRangeByRankResult, err := client.ZRemRangeByRank(key1, 2, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByRankResult) + + // Remove first two members + zRemRangeByRankResult, err = client.ZRemRangeByRank(key1, 0, 1) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(2), zRemRangeByRankResult) + + // Remove all members + zRemRangeByRankResult, err = client.ZRemRangeByRank(key1, 0, 10) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), zRemRangeByRankResult) + + zRangeWithScoresResult, err := client.ZRangeWithScores(key1, options.NewRangeByIndexQuery(0, -1)) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), 0, len(zRangeWithScoresResult)) + + // Non-existing key + zRemRangeByRankResult, err = client.ZRemRangeByRank("non_existing_key", 0, 10) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByRankResult) + + // Key exists, but it is not a set + setResult, err := client.Set(stringKey, "test") + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", setResult) + + _, err = client.ZRemRangeByRank(stringKey, 0, 10) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + +func (suite *GlideTestSuite) TestZRemRangeByLex() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := uuid.New().String() + stringKey := uuid.New().String() + + // Add members to the set + zAddResult, err := client.ZAdd(key1, map[string]float64{"a": 1.0, "b": 2.0, "c": 3.0, "d": 4.0}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(4), zAddResult) + + // min > max + zRemRangeByLexResult, err := client.ZRemRangeByLex( + key1, + *options.NewRangeByLexQuery(options.NewLexBoundary("d", false), options.NewLexBoundary("a", false)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByLexResult) + + // Remove members with lexicographical range + zRemRangeByLexResult, err = client.ZRemRangeByLex( + key1, + *options.NewRangeByLexQuery(options.NewLexBoundary("a", false), options.NewLexBoundary("c", true)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(2), zRemRangeByLexResult) + + zRemRangeByLexResult, err = client.ZRemRangeByLex( + key1, + *options.NewRangeByLexQuery(options.NewLexBoundary("d", true), options.NewInfiniteLexBoundary(options.PositiveInfinity)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), zRemRangeByLexResult) + + // Non-existing key + zRemRangeByLexResult, err = client.ZRemRangeByLex( + "non_existing_key", + *options.NewRangeByLexQuery(options.NewLexBoundary("a", false), options.NewLexBoundary("c", false)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByLexResult) + + // Key exists, but it is not a set + setResult, err := client.Set(stringKey, "test") + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", setResult) + + _, err = client.ZRemRangeByLex( + stringKey, + *options.NewRangeByLexQuery(options.NewLexBoundary("a", false), options.NewLexBoundary("c", false)), + ) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + +func (suite *GlideTestSuite) TestZRemRangeByScore() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key1 := uuid.New().String() + stringKey := uuid.New().String() + + // Add members to the set + zAddResult, err := client.ZAdd(key1, map[string]float64{"one": 1.0, "two": 2.0, "three": 3.0, "four": 4.0}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(4), zAddResult) + + // min > max + zRemRangeByScoreResult, err := client.ZRemRangeByScore( + key1, + *options.NewRangeByScoreQuery(options.NewScoreBoundary(2.0, false), options.NewScoreBoundary(1.0, false)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByScoreResult) + + // Remove members with score range + zRemRangeByScoreResult, err = client.ZRemRangeByScore( + key1, + *options.NewRangeByScoreQuery(options.NewScoreBoundary(1.0, false), options.NewScoreBoundary(3.0, true)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(2), zRemRangeByScoreResult) + + // Remove all members + zRemRangeByScoreResult, err = client.ZRemRangeByScore( + key1, + *options.NewRangeByScoreQuery(options.NewScoreBoundary(1.0, false), options.NewScoreBoundary(10.0, true)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), zRemRangeByScoreResult) + + // Non-existing key + zRemRangeByScoreResult, err = client.ZRemRangeByScore( + "non_existing_key", + *options.NewRangeByScoreQuery(options.NewScoreBoundary(1.0, false), options.NewScoreBoundary(10.0, true)), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), zRemRangeByScoreResult) + + // Key exists, but it is not a set + setResult, err := client.Set(stringKey, "test") + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "OK", setResult) + + _, err = client.ZRemRangeByScore( + stringKey, + *options.NewRangeByScoreQuery(options.NewScoreBoundary(1.0, false), options.NewScoreBoundary(10.0, true)), + ) + assert.NotNil(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} From 043c51dcee6223e0c81b0bfee1ca3993a1c70e0c Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Fri, 17 Jan 2025 14:18:46 -0800 Subject: [PATCH 08/21] Go: XAUTOCLAIM. (#2955) * Go: `XAUTOCLAIM`. Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 251 +++++++++++++++++++++++++++ go/api/options/stream_options.go | 14 ++ go/api/response_handlers.go | 96 ++++++++++ go/api/response_types.go | 14 ++ go/api/stream_commands.go | 28 +++ go/integTest/shared_commands_test.go | 134 ++++++++++++-- 6 files changed, 525 insertions(+), 12 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index ebd529c285..812030a2ab 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -4949,6 +4949,257 @@ func (client *Command) XLen(key string) (int64, error) { return handleIntResponse(result) } +// Transfers ownership of pending stream entries that match the specified criteria. +// +// Since: +// +// Valkey 6.2.0 and above. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// consumer - The group consumer. +// minIdleTime - The minimum idle time for the message to be claimed. +// start - Filters the claimed entries to those that have an ID equal or greater than the specified value. +// +// Return value: +// +// An object containing the following elements: +// - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is +// equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if +// the entire stream was scanned. +// - A map of the claimed entries. +// - If you are using Valkey 7.0.0 or above, the response will also include an array containing +// the message IDs that were in the Pending Entries List but no longer exist in the stream. +// These IDs are deleted from the Pending Entries List. +// +// Example: +// +// result, err := client.XAutoClaim("myStream", "myGroup", "myConsumer", 42, "0-0") +// result: +// // &{ +// // "1609338788321-0" // value to be used as `start` argument for the next `xautoclaim` call +// // map[ +// // "1609338752495-0": [ // claimed entries +// // ["field 1", "value 1"] +// // ["field 2", "value 2"] +// // ] +// // ] +// // [ +// // "1594324506465-0", // array of IDs of deleted messages, +// // "1594568784150-0" // included in the response only on valkey 7.0.0 and above +// // ] +// // } +// +// [valkey.io]: https://valkey.io/commands/xautoclaim/ +func (client *baseClient) XAutoClaim( + key string, + group string, + consumer string, + minIdleTime int64, + start string, +) (XAutoClaimResponse, error) { + return client.XAutoClaimWithOptions(key, group, consumer, minIdleTime, start, nil) +} + +// Transfers ownership of pending stream entries that match the specified criteria. +// +// Since: +// +// Valkey 6.2.0 and above. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// consumer - The group consumer. +// minIdleTime - The minimum idle time for the message to be claimed. +// start - Filters the claimed entries to those that have an ID equal or greater than the specified value. +// options - Options detailing how to read the stream. +// +// Return value: +// +// An object containing the following elements: +// - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is +// equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if +// the entire stream was scanned. +// - A map of the claimed entries. +// - If you are using Valkey 7.0.0 or above, the response will also include an array containing +// the message IDs that were in the Pending Entries List but no longer exist in the stream. +// These IDs are deleted from the Pending Entries List. +// +// Example: +// +// opts := options.NewXAutoClaimOptionsWithCount(1) +// result, err := client.XAutoClaimWithOptions("myStream", "myGroup", "myConsumer", 42, "0-0", opts) +// result: +// // &{ +// // "1609338788321-0" // value to be used as `start` argument for the next `xautoclaim` call +// // map[ +// // "1609338752495-0": [ // claimed entries +// // ["field 1", "value 1"] +// // ["field 2", "value 2"] +// // ] +// // ] +// // [ +// // "1594324506465-0", // array of IDs of deleted messages, +// // "1594568784150-0" // included in the response only on valkey 7.0.0 and above +// // ] +// // } +// +// [valkey.io]: https://valkey.io/commands/xautoclaim/ +func (client *baseClient) XAutoClaimWithOptions( + key string, + group string, + consumer string, + minIdleTime int64, + start string, + options *options.XAutoClaimOptions, +) (XAutoClaimResponse, error) { + args := []string{key, group, consumer, utils.IntToString(minIdleTime), start} + if options != nil { + optArgs, err := options.ToArgs() + if err != nil { + return XAutoClaimResponse{}, err + } + args = append(args, optArgs...) + } + result, err := client.executeCommand(C.XAutoClaim, args) + if err != nil { + return XAutoClaimResponse{}, err + } + return handleXAutoClaimResponse(result) +} + +// Transfers ownership of pending stream entries that match the specified criteria. +// +// Since: +// +// Valkey 6.2.0 and above. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// consumer - The group consumer. +// minIdleTime - The minimum idle time for the message to be claimed. +// start - Filters the claimed entries to those that have an ID equal or greater than the specified value. +// +// Return value: +// +// An object containing the following elements: +// - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is +// equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if +// the entire stream was scanned. +// - An array of IDs for the claimed entries. +// - If you are using Valkey 7.0.0 or above, the response will also include an array containing +// the message IDs that were in the Pending Entries List but no longer exist in the stream. +// These IDs are deleted from the Pending Entries List. +// +// Example: +// +// result, err := client.XAutoClaimJustId("myStream", "myGroup", "myConsumer", 42, "0-0") +// result: +// // &{ +// // "1609338788321-0" // value to be used as `start` argument for the next `xautoclaim` call +// // [ +// // "1609338752495-0", // claimed entries +// // "1609338752495-1" +// // ] +// // [ +// // "1594324506465-0", // array of IDs of deleted messages, +// // "1594568784150-0" // included in the response only on valkey 7.0.0 and above +// // ] +// // } +// +// [valkey.io]: https://valkey.io/commands/xautoclaim/ +func (client *baseClient) XAutoClaimJustId( + key string, + group string, + consumer string, + minIdleTime int64, + start string, +) (XAutoClaimJustIdResponse, error) { + return client.XAutoClaimJustIdWithOptions(key, group, consumer, minIdleTime, start, nil) +} + +// Transfers ownership of pending stream entries that match the specified criteria. +// +// Since: +// +// Valkey 6.2.0 and above. +// +// See [valkey.io] for more details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// consumer - The group consumer. +// minIdleTime - The minimum idle time for the message to be claimed. +// start - Filters the claimed entries to those that have an ID equal or greater than the specified value. +// options - Options detailing how to read the stream. +// +// Return value: +// +// An object containing the following elements: +// - A stream ID to be used as the start argument for the next call to `XAUTOCLAIM`. This ID is +// equivalent to the next ID in the stream after the entries that were scanned, or "0-0" if +// the entire stream was scanned. +// - An array of IDs for the claimed entries. +// - If you are using Valkey 7.0.0 or above, the response will also include an array containing +// the message IDs that were in the Pending Entries List but no longer exist in the stream. +// These IDs are deleted from the Pending Entries List. +// +// Example: +// +// opts := options.NewXAutoClaimOptionsWithCount(1) +// result, err := client.XAutoClaimJustIdWithOptions("myStream", "myGroup", "myConsumer", 42, "0-0", opts) +// result: +// // &{ +// // "1609338788321-0" // value to be used as `start` argument for the next `xautoclaim` call +// // [ +// // "1609338752495-0", // claimed entries +// // "1609338752495-1" +// // ] +// // [ +// // "1594324506465-0", // array of IDs of deleted messages, +// // "1594568784150-0" // included in the response only on valkey 7.0.0 and above +// // ] +// // } +// +// [valkey.io]: https://valkey.io/commands/xautoclaim/ +func (client *baseClient) XAutoClaimJustIdWithOptions( + key string, + group string, + consumer string, + minIdleTime int64, + start string, + options *options.XAutoClaimOptions, +) (XAutoClaimJustIdResponse, error) { + args := []string{key, group, consumer, utils.IntToString(minIdleTime), start} + if options != nil { + optArgs, err := options.ToArgs() + if err != nil { + return XAutoClaimJustIdResponse{}, err + } + args = append(args, optArgs...) + } + args = append(args, "JUSTID") + result, err := client.executeCommand(C.XAutoClaim, args) + if err != nil { + return XAutoClaimJustIdResponse{}, err + } + return handleXAutoClaimJustIdResponse(result) +} + // Removes the specified entries by id from a stream, and returns the number of entries deleted. // // See [valkey.io] for details. diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index ff40c224ac..4507b0478c 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -116,6 +116,20 @@ func (xTrimOptions *XTrimOptions) ToArgs() ([]string, error) { return args, nil } +// Optional arguments for `XAutoClaim` in [StreamCommands] +type XAutoClaimOptions struct { + count int64 +} + +// Option to trim the stream according to minimum ID. +func NewXAutoClaimOptionsWithCount(count int64) *XAutoClaimOptions { + return &XAutoClaimOptions{count} +} + +func (xacp *XAutoClaimOptions) ToArgs() ([]string, error) { + return []string{"COUNT", utils.IntToString(xacp.count)}, nil +} + // Optional arguments for `XRead` in [StreamCommands] type XReadOptions struct { count, block int64 diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index c848cdc57e..d8b3a734e2 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -603,6 +603,102 @@ func (node arrayConverter[T]) convert(data interface{}) (interface{}, error) { // TODO: convert sets +func handleXAutoClaimResponse(response *C.struct_CommandResponse) (XAutoClaimResponse, error) { + defer C.free_command_response(response) + var null XAutoClaimResponse // default response + typeErr := checkResponseType(response, C.Array, false) + if typeErr != nil { + return null, typeErr + } + slice, err := parseArray(response) + if err != nil { + return null, err + } + arr := slice.([]interface{}) + len := len(arr) + if len < 2 || len > 3 { + return null, &RequestError{fmt.Sprintf("Unexpected response array length: %d", len)} + } + converted, err := mapConverter[[][]string]{ + arrayConverter[[]string]{ + arrayConverter[string]{ + nil, + false, + }, + false, + }, + false, + }.convert(arr[1]) + if err != nil { + return null, err + } + claimedEntries, ok := converted.(map[string][][]string) + if !ok { + return null, &RequestError{fmt.Sprintf("unexpected type of second element: %T", converted)} + } + var deletedMessages []string + deletedMessages = nil + if len == 3 { + converted, err = arrayConverter[string]{ + nil, + false, + }.convert(arr[2]) + if err != nil { + return null, err + } + deletedMessages, ok = converted.([]string) + if !ok { + return null, &RequestError{fmt.Sprintf("unexpected type of third element: %T", converted)} + } + } + return XAutoClaimResponse{arr[0].(string), claimedEntries, deletedMessages}, nil +} + +func handleXAutoClaimJustIdResponse(response *C.struct_CommandResponse) (XAutoClaimJustIdResponse, error) { + defer C.free_command_response(response) + var null XAutoClaimJustIdResponse // default response + typeErr := checkResponseType(response, C.Array, false) + if typeErr != nil { + return null, typeErr + } + slice, err := parseArray(response) + if err != nil { + return null, err + } + arr := slice.([]interface{}) + len := len(arr) + if len < 2 || len > 3 { + return null, &RequestError{fmt.Sprintf("Unexpected response array length: %d", len)} + } + converted, err := arrayConverter[string]{ + nil, + false, + }.convert(arr[1]) + if err != nil { + return null, err + } + claimedEntries, ok := converted.([]string) + if !ok { + return null, &RequestError{fmt.Sprintf("unexpected type of second element: %T", converted)} + } + var deletedMessages []string + deletedMessages = nil + if len == 3 { + converted, err = arrayConverter[string]{ + nil, + false, + }.convert(arr[2]) + if err != nil { + return null, err + } + deletedMessages, ok = converted.([]string) + if !ok { + return null, &RequestError{fmt.Sprintf("unexpected type of third element: %T", converted)} + } + } + return XAutoClaimJustIdResponse{arr[0].(string), claimedEntries, deletedMessages}, nil +} + func handleXReadResponse(response *C.struct_CommandResponse) (map[string]map[string][][]string, error) { defer C.free_command_response(response) data, err := parseMap(response) diff --git a/go/api/response_types.go b/go/api/response_types.go index 2e6e527c43..84de6aed7f 100644 --- a/go/api/response_types.go +++ b/go/api/response_types.go @@ -23,6 +23,20 @@ type KeyWithMemberAndScore struct { Score float64 } +// Response type of [XAutoClaim] command. +type XAutoClaimResponse struct { + NextEntry string + ClaimedEntries map[string][][]string + DeletedMessages []string +} + +// Response type of [XAutoClaimJustId] command. +type XAutoClaimJustIdResponse struct { + NextEntry string + ClaimedEntries []string + DeletedMessages []string +} + func (result Result[T]) IsNil() bool { return result.isNil } diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 46ddd092fc..564b8b8109 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -19,6 +19,34 @@ type StreamCommands interface { XLen(key string) (int64, error) + XAutoClaim(key string, group string, consumer string, minIdleTime int64, start string) (XAutoClaimResponse, error) + + XAutoClaimWithOptions( + key string, + group string, + consumer string, + minIdleTime int64, + start string, + options *options.XAutoClaimOptions, + ) (XAutoClaimResponse, error) + + XAutoClaimJustId( + key string, + group string, + consumer string, + minIdleTime int64, + start string, + ) (XAutoClaimJustIdResponse, error) + + XAutoClaimJustIdWithOptions( + key string, + group string, + consumer string, + minIdleTime int64, + start string, + options *options.XAutoClaimOptions, + ) (XAutoClaimJustIdResponse, error) + XReadGroup(group string, consumer string, keysAndIds map[string]string) (map[string]map[string][][]string, error) XReadGroupWithOptions( diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 32dcb6a055..507df5e959 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4122,6 +4122,122 @@ func sendWithCustomCommand(suite *GlideTestSuite, client api.BaseClient, args [] return res } +func (suite *GlideTestSuite) TestXAutoClaim() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.NewString() + group := uuid.NewString() + consumer := uuid.NewString() + + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key, group, "0", "MKSTREAM"}, + "Can't send XGROUP CREATE as a custom command", + ) + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "createconsumer", key, group, consumer}, + "Can't send XGROUP CREATECONSUMER as a custom command", + ) + + xadd, err := client.XAddWithOptions( + key, + [][]string{{"entry1_field1", "entry1_value1"}, {"entry1_field2", "entry1_value2"}}, + options.NewXAddOptions().SetId("0-1"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "0-1", xadd.Value()) + xadd, err = client.XAddWithOptions( + key, + [][]string{{"entry2_field1", "entry2_value1"}}, + options.NewXAddOptions().SetId("0-2"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "0-2", xadd.Value()) + + 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: { + "0-1": {{"entry1_field1", "entry1_value1"}, {"entry1_field2", "entry1_value2"}}, + "0-2": {{"entry2_field1", "entry2_value1"}}, + }, + }, xreadgroup) + + opts := options.NewXAutoClaimOptionsWithCount(1) + xautoclaim, err := client.XAutoClaimWithOptions(key, group, consumer, 0, "0-0", opts) + assert.NoError(suite.T(), err) + var deletedEntries []string + if suite.serverVersion >= "7.0.0" { + deletedEntries = []string{} + } + assert.Equal( + suite.T(), + api.XAutoClaimResponse{ + NextEntry: "0-2", + ClaimedEntries: map[string][][]string{ + "0-1": {{"entry1_field1", "entry1_value1"}, {"entry1_field2", "entry1_value2"}}, + }, + DeletedMessages: deletedEntries, + }, + xautoclaim, + ) + + justId, err := client.XAutoClaimJustId(key, group, consumer, 0, "0-0") + assert.NoError(suite.T(), err) + assert.Equal( + suite.T(), + api.XAutoClaimJustIdResponse{ + NextEntry: "0-0", + ClaimedEntries: []string{"0-1", "0-2"}, + DeletedMessages: deletedEntries, + }, + justId, + ) + + // add one more entry + xadd, err = client.XAddWithOptions( + key, + [][]string{{"entry3_field1", "entry3_value1"}}, + options.NewXAddOptions().SetId("0-3"), + ) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), "0-3", xadd.Value()) + + // incorrect IDs - response is empty + xautoclaim, err = client.XAutoClaim(key, group, consumer, 0, "5-0") + assert.NoError(suite.T(), err) + assert.Equal( + suite.T(), + api.XAutoClaimResponse{ + NextEntry: "0-0", + ClaimedEntries: map[string][][]string{}, + DeletedMessages: deletedEntries, + }, + xautoclaim, + ) + + justId, err = client.XAutoClaimJustId(key, group, consumer, 0, "5-0") + assert.NoError(suite.T(), err) + assert.Equal( + suite.T(), + api.XAutoClaimJustIdResponse{ + NextEntry: "0-0", + ClaimedEntries: []string{}, + DeletedMessages: deletedEntries, + }, + justId, + ) + + // key exists, but it is not a stream + key2 := uuid.New().String() + suite.verifyOK(client.Set(key2, key2)) + _, err = client.XAutoClaim(key2, "_", "_", 0, "_") + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + func (suite *GlideTestSuite) TestXReadGroup() { suite.runWithDefaultClients(func(client api.BaseClient) { key1 := "{xreadgroup}-1-" + uuid.NewString() @@ -5389,8 +5505,7 @@ func (suite *GlideTestSuite) TestXPending() { streamid_2, err := client.XAdd(key, [][]string{{"field2", "value2"}}) assert.NoError(suite.T(), err) - command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer1, map[string]string{key: ">"}) assert.NoError(suite.T(), err) _, err = client.XAdd(key, [][]string{{"field3", "value3"}}) @@ -5400,8 +5515,7 @@ func (suite *GlideTestSuite) TestXPending() { streamid_5, err := client.XAdd(key, [][]string{{"field5", "value5"}}) assert.NoError(suite.T(), err) - command = []string{"XReadGroup", "GROUP", groupName, consumer2, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer2, map[string]string{key: ">"}) assert.NoError(suite.T(), err) expectedSummary := api.XPendingSummary{ @@ -5465,8 +5579,7 @@ func (suite *GlideTestSuite) TestXPending() { streamid_2, err := client.XAdd(key, [][]string{{"field2", "value2"}}) assert.NoError(suite.T(), err) - command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer1, map[string]string{key: ">"}) assert.NoError(suite.T(), err) _, err = client.XAdd(key, [][]string{{"field3", "value3"}}) @@ -5476,8 +5589,7 @@ func (suite *GlideTestSuite) TestXPending() { streamid_5, err := client.XAdd(key, [][]string{{"field5", "value5"}}) assert.NoError(suite.T(), err) - command = []string{"XReadGroup", "GROUP", groupName, consumer2, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer2, map[string]string{key: ">"}) assert.NoError(suite.T(), err) expectedSummary := api.XPendingSummary{ @@ -5574,8 +5686,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { assert.Equal(suite.T(), 0, len(detailResult)) // read the entire stream for the consumer and mark messages as pending - command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer1, map[string]string{key: ">"}) assert.NoError(suite.T(), err) // sanity check - expect some results: @@ -5727,8 +5838,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { assert.Equal(suite.T(), 0, len(detailResult)) // read the entire stream for the consumer and mark messages as pending - command = []string{"XReadGroup", "GROUP", groupName, consumer1, "STREAMS", key, ">"} - _, err = client.CustomCommand(command) + _, err = client.XReadGroup(groupName, consumer1, map[string]string{key: ">"}) assert.NoError(suite.T(), err) // sanity check - expect some results: From 7349b1af9b0395a4463bd81a748ec3187b1a9a35 Mon Sep 17 00:00:00 2001 From: tjzhang-BQ <111323543+tjzhang-BQ@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:13:54 -0800 Subject: [PATCH 09/21] Go: update return types & response handlers for scan commands (#2956) * Go: update return types & response handlers for scan commands Signed-off-by: TJ Zhang Signed-off-by: Edward Liang --- go/api/base_client.go | 134 ++++++++++++++------------- go/api/hash_commands.go | 5 +- go/api/response_handlers.go | 22 ++--- go/api/set_commands.go | 5 +- go/api/sorted_set_commands.go | 4 +- go/integTest/shared_commands_test.go | 122 +++++++++++------------- go/integTest/test_utils.go | 20 ++-- 7 files changed, 148 insertions(+), 164 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 812030a2ab..d132a20c4b 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1243,24 +1243,24 @@ func (client *Command) HIncrByFloat(key string, field string, increment float64) // // Return value: // -// An array of the cursor and the subset of the hash held by `key`. The first element is always the `cursor` -// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the subset. -// The second element is always an array of the subset of the set held in `key`. The array in the -// second element is always a flattened series of String pairs, where the key is at even indices -// and the value is at odd indices. +// An array of the cursor and the subset of the hash held by `key`. The first element is always the `cursor` +// for the next iteration of results. The `cursor` will be `"0"` on the last iteration of the subset. +// The second element is always an array of the subset of the set held in `key`. The array in the +// second element is always a flattened series of String pairs, where the key is at even indices +// and the value is at odd indices. // // Example: // -// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} -// resCursor, resCollection, err = client.HScan(key, initialCursor) -// // resCursor = {0 false} -// // resCollection = [{a false} {1 false} {b false} {2 false}] +// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} +// resCursor, resCollection, err = client.HScan(key, initialCursor) +// resCursor = {0 false} +// resCollection = [{a false} {1 false} {b false} {2 false}] // // [valkey.io]: https://valkey.io/commands/hscan/ -func (client *Command) HScan(key string, cursor string) (Result[string], []Result[string], error) { +func (client *baseClient) HScan(key string, cursor string) (string, []string, error) { result, err := client.executeCommand(C.HScan, []string{key, cursor}) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } @@ -1295,19 +1295,19 @@ func (client *Command) HScan(key string, cursor string) (Result[string], []Resul // // input. // // [valkey.io]: https://valkey.io/commands/hscan/ -func (client *Command) HScanWithOptions( +func (client *baseClient) HScanWithOptions( key string, cursor string, options *options.HashScanOptions, -) (Result[string], []Result[string], error) { +) (string, []string, error) { optionArgs, err := options.ToArgs() if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } result, err := client.executeCommand(C.HScan, append([]string{key, cursor}, optionArgs...)) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } @@ -2126,26 +2126,28 @@ func (client *Command) SUnion(keys []string) (map[Result[string]]struct{}, error // // Example: // -// // assume "key" contains a set -// resCursor, resCol, err := client.sscan("key", "0") -// for resCursor != "0" { -// resCursor, resCol, err = client.sscan("key", "0") -// fmt.Println("Cursor: ", resCursor.Value()) -// fmt.Println("Members: ", resCol.Value()) -// } -// // Output: -// // Cursor: 48 -// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] -// // Cursor: 24 -// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] -// // Cursor: 0 -// // Members: ['47', '122', '1', '53', '10', '14', '80'] +// // assume "key" contains a set +// resCursor, resCol, err := client.sscan("key", "0") +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) +// for resCursor != "0" { +// resCursor, resCol, err = client.sscan("key", "0") +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) +// } +// // Output: +// // Cursor: 48 +// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] +// // Cursor: 24 +// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] +// // Cursor: 0 +// // Members: ['47', '122', '1', '53', '10', '14', '80'] // // [valkey.io]: https://valkey.io/commands/sscan/ -func (client *Command) SScan(key string, cursor string) (Result[string], []Result[string], error) { +func (client *baseClient) SScan(key string, cursor string) (string, []string, error) { result, err := client.executeCommand(C.SScan, []string{key, cursor}) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } @@ -2172,36 +2174,38 @@ func (client *Command) SScan(key string, cursor string) (Result[string], []Resul // // Example: // -// // assume "key" contains a set -// resCursor resCol, err := client.sscan("key", "0", opts) -// for resCursor != "0" { -// opts := options.NewBaseScanOptionsBuilder().SetMatch("*") -// resCursor, resCol, err = client.sscan("key", "0", opts) -// fmt.Println("Cursor: ", resCursor.Value()) -// fmt.Println("Members: ", resCol.Value()) -// } -// // Output: -// // Cursor: 48 -// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] -// // Cursor: 24 -// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] -// // Cursor: 0 -// // Members: ['47', '122', '1', '53', '10', '14', '80'] +// // assume "key" contains a set +// resCursor, resCol, err := client.sscan("key", "0", opts) +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) +// for resCursor != "0" { +// opts := options.NewBaseScanOptionsBuilder().SetMatch("*") +// resCursor, resCol, err = client.sscan("key", "0", opts) +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) +// } +// // Output: +// // Cursor: 48 +// // Members: ['3', '118', '120', '86', '76', '13', '61', '111', '55', '45'] +// // Cursor: 24 +// // Members: ['38', '109', '11', '119', '34', '24', '40', '57', '20', '17'] +// // Cursor: 0 +// // Members: ['47', '122', '1', '53', '10', '14', '80'] // // [valkey.io]: https://valkey.io/commands/sscan/ -func (client *Command) SScanWithOptions( +func (client *baseClient) SScanWithOptions( key string, cursor string, options *options.BaseScanOptions, -) (Result[string], []Result[string], error) { +) (string, []string, error) { optionArgs, err := options.ToArgs() if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } result, err := client.executeCommand(C.SScan, append([]string{key, cursor}, optionArgs...)) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } @@ -5290,19 +5294,19 @@ func (client *baseClient) ZScore(key string, member string) (Result[float64], er // // // assume "key" contains a set // resCursor, resCol, err := client.ZScan("key", "0") -// fmt.Println(resCursor.Value()) -// fmt.Println(resCol.Value()) +// fmt.Println(resCursor) +// fmt.Println(resCol) // for resCursor != "0" { -// resCursor, resCol, err = client.ZScan("key", resCursor.Value()) -// fmt.Println("Cursor: ", resCursor.Value()) -// fmt.Println("Members: ", resCol.Value()) +// resCursor, resCol, err = client.ZScan("key", resCursor) +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) // } // // [valkey.io]: https://valkey.io/commands/zscan/ -func (client *baseClient) ZScan(key string, cursor string) (Result[string], []Result[string], error) { +func (client *baseClient) ZScan(key string, cursor string) (string, []string, error) { result, err := client.executeCommand(C.ZScan, []string{key, cursor}) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } @@ -5328,13 +5332,13 @@ func (client *baseClient) ZScan(key string, cursor string) (Result[string], []Re // Example: // // resCursor, resCol, err := client.ZScanWithOptions("key", "0", options.NewBaseScanOptionsBuilder().SetMatch("*")) -// fmt.Println(resCursor.Value()) -// fmt.Println(resCol.Value()) +// fmt.Println(resCursor) +// fmt.Println(resCol) // for resCursor != "0" { -// resCursor, resCol, err = client.ZScanWithOptions("key", resCursor.Value(), +// resCursor, resCol, err = client.ZScanWithOptions("key", resCursor, // options.NewBaseScanOptionsBuilder().SetMatch("*")) -// fmt.Println("Cursor: ", resCursor.Value()) -// fmt.Println("Members: ", resCol.Value()) +// fmt.Println("Cursor: ", resCursor) +// fmt.Println("Members: ", resCol) // } // // [valkey.io]: https://valkey.io/commands/zscan/ @@ -5342,15 +5346,15 @@ func (client *baseClient) ZScanWithOptions( key string, cursor string, options *options.ZScanOptions, -) (Result[string], []Result[string], error) { +) (string, []string, error) { optionArgs, err := options.ToArgs() if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } result, err := client.executeCommand(C.ZScan, append([]string{key, cursor}, optionArgs...)) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } return handleScanResponse(result) } diff --git a/go/api/hash_commands.go b/go/api/hash_commands.go index 035ed95968..e5100a1d23 100644 --- a/go/api/hash_commands.go +++ b/go/api/hash_commands.go @@ -10,7 +10,6 @@ import "github.com/valkey-io/valkey-glide/go/glide/api/options" // // [valkey.io]: https://valkey.io/commands/#hash type HashCommands interface { - HGet(key string, field string) (Result[string], error) HGetAll(key string) (map[Result[string]]Result[string], error) @@ -37,7 +36,7 @@ type HashCommands interface { HIncrByFloat(key string, field string, increment float64) (float64, error) - HScan(key string, cursor string) (Result[string], []Result[string], error) + HScan(key string, cursor string) (string, []string, error) - HScanWithOptions(key string, cursor string, options *options.HashScanOptions) (Result[string], []Result[string], error) + HScanWithOptions(key string, cursor string, options *options.HashScanOptions) (string, []string, error) } diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index d8b3a734e2..48a7dc7509 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -461,40 +461,38 @@ func handleKeyWithMemberAndScoreResponse(response *C.struct_CommandResponse) (Re return CreateKeyWithMemberAndScoreResult(KeyWithMemberAndScore{key, member, score}), nil } -func handleScanResponse( - response *C.struct_CommandResponse, -) (Result[string], []Result[string], error) { +func handleScanResponse(response *C.struct_CommandResponse) (string, []string, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Array, false) if typeErr != nil { - return CreateNilStringResult(), nil, typeErr + return "", nil, typeErr } slice, err := parseArray(response) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } if arr, ok := slice.([]interface{}); ok { - resCollection, err := convertToResultStringArray(arr[1].([]interface{})) + resCollection, err := convertToStringArray(arr[1].([]interface{})) if err != nil { - return CreateNilStringResult(), nil, err + return "", nil, err } - return CreateStringResult(arr[0].(string)), resCollection, nil + return arr[0].(string), resCollection, nil } - return CreateNilStringResult(), nil, err + return "", nil, err } -func convertToResultStringArray(input []interface{}) ([]Result[string], error) { - result := make([]Result[string], len(input)) +func convertToStringArray(input []interface{}) ([]string, error) { + result := make([]string, len(input)) for i, v := range input { str, ok := v.(string) if !ok { return nil, fmt.Errorf("element at index %d is not a string: %v", i, v) } - result[i] = CreateStringResult(str) + result[i] = str } return result, nil } diff --git a/go/api/set_commands.go b/go/api/set_commands.go index 099d901a83..6dcc2290eb 100644 --- a/go/api/set_commands.go +++ b/go/api/set_commands.go @@ -10,7 +10,6 @@ import "github.com/valkey-io/valkey-glide/go/glide/api/options" // // [valkey.io]: https://valkey.io/commands/#set type SetCommands interface { - SAdd(key string, members []string) (int64, error) SRem(key string, members []string) (int64, error) @@ -43,9 +42,9 @@ type SetCommands interface { SUnion(keys []string) (map[Result[string]]struct{}, error) - SScan(key string, cursor string) (Result[string], []Result[string], error) + SScan(key string, cursor string) (string, []string, error) - SScanWithOptions(key string, cursor string, options *options.BaseScanOptions) (Result[string], []Result[string], error) + SScanWithOptions(key string, cursor string, options *options.BaseScanOptions) (string, []string, error) SMove(source string, destination string, member string) (bool, error) } diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index 75107163eb..f6cd9f9256 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -53,9 +53,9 @@ type SortedSetCommands interface { ZCount(key string, rangeOptions *options.ZCountRange) (int64, error) - ZScan(key string, cursor string) (Result[string], []Result[string], error) + ZScan(key string, cursor string) (string, []string, error) - ZScanWithOptions(key string, cursor string, options *options.ZScanOptions) (Result[string], []Result[string], error) + ZScanWithOptions(key string, cursor string, options *options.ZScanOptions) (string, []string, error) ZRemRangeByLex(key string, rangeQuery options.RangeByLex) (int64, error) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 507df5e959..184cf3e430 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -1139,7 +1139,7 @@ func (suite *GlideTestSuite) TestHScan() { // Check for empty set. resCursor, resCollection, err := client.HScan(key1, initialCursor) assert.NoError(t, err) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Empty(t, resCollection) // Negative cursor check. @@ -1148,7 +1148,7 @@ func (suite *GlideTestSuite) TestHScan() { assert.NotEmpty(t, err) } else { resCursor, resCollection, _ = client.HScan(key1, "-1") - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Empty(t, resCollection) } @@ -1157,27 +1157,27 @@ func (suite *GlideTestSuite) TestHScan() { assert.Equal(t, int64(len(charMembers)), hsetResult) resCursor, resCollection, _ = client.HScan(key1, initialCursor) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) // Length includes the score which is twice the map size assert.Equal(t, len(charMap)*2, len(resCollection)) - resultKeys := make([]api.Result[string], 0) - resultValues := make([]api.Result[string], 0) + resultKeys := make([]string, 0) + resultValues := make([]string, 0) for i := 0; i < len(resCollection); i += 2 { resultKeys = append(resultKeys, resCollection[i]) resultValues = append(resultValues, resCollection[i+1]) } - keysList, valuesList := convertMapKeysAndValuesToResultList(charMap) + keysList, valuesList := convertMapKeysAndValuesToLists(charMap) assert.True(t, isSubset(resultKeys, keysList) && isSubset(keysList, resultKeys)) assert.True(t, isSubset(resultValues, valuesList) && isSubset(valuesList, resultValues)) opts := options.NewHashScanOptionsBuilder().SetMatch("a") resCursor, resCollection, _ = client.HScanWithOptions(key1, initialCursor, opts) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Equal(t, len(resCollection), 2) - assert.Equal(t, resCollection[0].Value(), "a") - assert.Equal(t, resCollection[1].Value(), "0") + assert.Equal(t, resCollection[0], "a") + assert.Equal(t, resCollection[1], "0") // Result contains a subset of the key combinedMap := make(map[string]string) @@ -1191,12 +1191,12 @@ func (suite *GlideTestSuite) TestHScan() { hsetResult, _ = client.HSet(key1, combinedMap) assert.Equal(t, int64(len(numberMap)), hsetResult) resultCursor := "0" - secondResultAllKeys := make([]api.Result[string], 0) - secondResultAllValues := make([]api.Result[string], 0) + secondResultAllKeys := make([]string, 0) + secondResultAllValues := make([]string, 0) isFirstLoop := true for { resCursor, resCollection, _ = client.HScan(key1, resultCursor) - resultCursor = resCursor.Value() + resultCursor = resCursor for i := 0; i < len(resCollection); i += 2 { secondResultAllKeys = append(secondResultAllKeys, resCollection[i]) secondResultAllValues = append(secondResultAllValues, resCollection[i+1]) @@ -1211,7 +1211,7 @@ func (suite *GlideTestSuite) TestHScan() { // Scan with result cursor to get the next set of data. newResultCursor, secondResult, _ := client.HScan(key1, resultCursor) assert.NotEqual(t, resultCursor, newResultCursor) - resultCursor = newResultCursor.Value() + resultCursor = newResultCursor assert.False(t, reflect.DeepEqual(secondResult, resCollection)) for i := 0; i < len(secondResult); i += 2 { secondResultAllKeys = append(secondResultAllKeys, secondResult[i]) @@ -1223,41 +1223,41 @@ func (suite *GlideTestSuite) TestHScan() { break } } - numberKeysList, numberValuesList := convertMapKeysAndValuesToResultList(numberMap) + numberKeysList, numberValuesList := convertMapKeysAndValuesToLists(numberMap) assert.True(t, isSubset(numberKeysList, secondResultAllKeys)) assert.True(t, isSubset(numberValuesList, secondResultAllValues)) // Test match pattern opts = options.NewHashScanOptionsBuilder().SetMatch("*") resCursor, resCollection, _ = client.HScanWithOptions(key1, initialCursor, opts) - resCursorInt, _ := strconv.Atoi(resCursor.Value()) + resCursorInt, _ := strconv.Atoi(resCursor) assert.True(t, resCursorInt >= 0) assert.True(t, int(len(resCollection)) >= defaultCount) // Test count opts = options.NewHashScanOptionsBuilder().SetCount(int64(20)) resCursor, resCollection, _ = client.HScanWithOptions(key1, initialCursor, opts) - resCursorInt, _ = strconv.Atoi(resCursor.Value()) + resCursorInt, _ = strconv.Atoi(resCursor) assert.True(t, resCursorInt >= 0) assert.True(t, len(resCollection) >= 20) // Test count with match returns a non-empty list opts = options.NewHashScanOptionsBuilder().SetMatch("1*").SetCount(int64(20)) resCursor, resCollection, _ = client.HScanWithOptions(key1, initialCursor, opts) - resCursorInt, _ = strconv.Atoi(resCursor.Value()) + resCursorInt, _ = strconv.Atoi(resCursor) assert.True(t, resCursorInt >= 0) assert.True(t, len(resCollection) >= 0) if suite.serverVersion >= "8.0.0" { opts = options.NewHashScanOptionsBuilder().SetNoValue(true) resCursor, resCollection, _ = client.HScanWithOptions(key1, initialCursor, opts) - resCursorInt, _ = strconv.Atoi(resCursor.Value()) + resCursorInt, _ = strconv.Atoi(resCursor) assert.True(t, resCursorInt >= 0) // Check if all fields don't start with "num" containsElementsWithNumKeyword := false for i := 0; i < len(resCollection); i++ { - if strings.Contains(resCollection[i].Value(), "num") { + if strings.Contains(resCollection[i], "num") { containsElementsWithNumKeyword = true break } @@ -2317,34 +2317,27 @@ func (suite *GlideTestSuite) TestSScan() { defaultCount := 10 // use large dataset to force an iterative cursor. numMembers := make([]string, 50000) - numMembersResult := make([]api.Result[string], 50000) + numMembersResult := make([]string, 50000) charMembers := []string{"a", "b", "c", "d", "e"} - charMembersResult := []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - api.CreateStringResult("c"), - api.CreateStringResult("d"), - api.CreateStringResult("e"), - } t := suite.T() // populate the dataset slice for i := 0; i < 50000; i++ { numMembers[i] = strconv.Itoa(i) - numMembersResult[i] = api.CreateStringResult(strconv.Itoa(i)) + numMembersResult[i] = strconv.Itoa(i) } // empty set resCursor, resCollection, err := client.SScan(key1, initialCursor) assert.NoError(t, err) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Empty(t, resCollection) // negative cursor if suite.serverVersion < "8.0.0" { resCursor, resCollection, err = client.SScan(key1, "-1") assert.NoError(t, err) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Empty(t, resCollection) } else { _, _, err = client.SScan(key1, "-1") @@ -2358,15 +2351,15 @@ func (suite *GlideTestSuite) TestSScan() { assert.Equal(t, int64(len(charMembers)), res) resCursor, resCollection, err = client.SScan(key1, initialCursor) assert.NoError(t, err) - assert.Equal(t, initialCursor, resCursor.Value()) + assert.Equal(t, initialCursor, resCursor) assert.Equal(t, len(charMembers), len(resCollection)) - assert.True(t, isSubset(resCollection, charMembersResult)) + assert.True(t, isSubset(resCollection, charMembers)) opts := options.NewBaseScanOptionsBuilder().SetMatch("a") resCursor, resCollection, err = client.SScanWithOptions(key1, initialCursor, opts) assert.NoError(t, err) - assert.Equal(t, initialCursor, resCursor.Value()) - assert.True(t, isSubset(resCollection, []api.Result[string]{api.CreateStringResult("a")})) + assert.Equal(t, initialCursor, resCursor) + assert.True(t, isSubset(resCollection, []string{"a"})) // result contains a subset of the key res, err = client.SAdd(key1, numMembers) @@ -2377,8 +2370,8 @@ func (suite *GlideTestSuite) TestSScan() { resultCollection := resCollection // 0 is returned for the cursor of the last iteration - for resCursor.Value() != "0" { - nextCursor, nextCol, err := client.SScan(key1, resCursor.Value()) + for resCursor != "0" { + nextCursor, nextCol, err := client.SScan(key1, resCursor) assert.NoError(t, err) assert.NotEqual(t, nextCursor, resCursor) assert.False(t, isSubset(resultCollection, nextCol)) @@ -2387,27 +2380,27 @@ func (suite *GlideTestSuite) TestSScan() { } assert.NotEmpty(t, resultCollection) assert.True(t, isSubset(numMembersResult, resultCollection)) - assert.True(t, isSubset(charMembersResult, resultCollection)) + assert.True(t, isSubset(charMembers, resultCollection)) // test match pattern opts = options.NewBaseScanOptionsBuilder().SetMatch("*") resCursor, resCollection, err = client.SScanWithOptions(key1, initialCursor, opts) assert.NoError(t, err) - assert.NotEqual(t, initialCursor, resCursor.Value()) + assert.NotEqual(t, initialCursor, resCursor) assert.GreaterOrEqual(t, len(resCollection), defaultCount) // test count opts = options.NewBaseScanOptionsBuilder().SetCount(20) resCursor, resCollection, err = client.SScanWithOptions(key1, initialCursor, opts) assert.NoError(t, err) - assert.NotEqual(t, initialCursor, resCursor.Value()) + assert.NotEqual(t, initialCursor, resCursor) assert.GreaterOrEqual(t, len(resCollection), 20) // test count with match, returns a non-empty array opts = options.NewBaseScanOptionsBuilder().SetMatch("1*").SetCount(20) resCursor, resCollection, err = client.SScanWithOptions(key1, initialCursor, opts) assert.NoError(t, err) - assert.NotEqual(t, initialCursor, resCursor.Value()) + assert.NotEqual(t, initialCursor, resCursor) assert.GreaterOrEqual(t, len(resCollection), 0) // exceptions @@ -5309,30 +5302,23 @@ func (suite *GlideTestSuite) TestZScan() { // Set up test data - use a large number of entries to force an iterative cursor numberMap := make(map[string]float64) - numMembersResult := make([]api.Result[string], 50000) + numMembers := make([]string, 50000) charMembers := []string{"a", "b", "c", "d", "e"} - charMembersResult := []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - api.CreateStringResult("c"), - api.CreateStringResult("d"), - api.CreateStringResult("e"), - } for i := 0; i < 50000; i++ { numberMap["member"+strconv.Itoa(i)] = float64(i) - numMembersResult[i] = api.CreateStringResult("member" + strconv.Itoa(i)) + numMembers[i] = "member" + strconv.Itoa(i) } charMap := make(map[string]float64) - charMapValues := []api.Result[string]{} + charMapValues := []string{} for i, val := range charMembers { charMap[val] = float64(i) - charMapValues = append(charMapValues, api.CreateStringResult(strconv.Itoa(i))) + charMapValues = append(charMapValues, strconv.Itoa(i)) } // Empty set resCursor, resCollection, err := client.ZScan(key1, initialCursor) assert.NoError(suite.T(), err) - assert.Equal(suite.T(), initialCursor, resCursor.Value()) + assert.Equal(suite.T(), initialCursor, resCursor) assert.Empty(suite.T(), resCollection) // Negative cursor @@ -5343,7 +5329,7 @@ func (suite *GlideTestSuite) TestZScan() { } else { resCursor, resCollection, err = client.ZScan(key1, "-1") assert.NoError(suite.T(), err) - assert.Equal(suite.T(), initialCursor, resCursor.Value()) + assert.Equal(suite.T(), initialCursor, resCursor) assert.Empty(suite.T(), resCollection) } @@ -5354,11 +5340,11 @@ func (suite *GlideTestSuite) TestZScan() { resCursor, resCollection, err = client.ZScan(key1, initialCursor) assert.NoError(suite.T(), err) - assert.Equal(suite.T(), initialCursor, resCursor.Value()) + assert.Equal(suite.T(), initialCursor, resCursor) assert.Equal(suite.T(), len(charMap)*2, len(resCollection)) - resultKeySet := make([]api.Result[string], 0, len(charMap)) - resultValueSet := make([]api.Result[string], 0, len(charMap)) + resultKeySet := make([]string, 0, len(charMap)) + resultValueSet := make([]string, 0, len(charMap)) // Iterate through array taking pairs of items for i := 0; i < len(resCollection); i += 2 { @@ -5367,7 +5353,7 @@ func (suite *GlideTestSuite) TestZScan() { } // Verify all expected keys exist in result - assert.True(suite.T(), isSubset(charMembersResult, resultKeySet)) + assert.True(suite.T(), isSubset(charMembers, resultKeySet)) // Scores come back as integers converted to a string when the fraction is zero. assert.True(suite.T(), isSubset(charMapValues, resultValueSet)) @@ -5375,8 +5361,8 @@ func (suite *GlideTestSuite) TestZScan() { opts := options.NewZScanOptionsBuilder().SetMatch("a") resCursor, resCollection, err = client.ZScanWithOptions(key1, initialCursor, opts) assert.NoError(suite.T(), err) - assert.Equal(suite.T(), initialCursor, resCursor.Value()) - assert.Equal(suite.T(), resCollection, []api.Result[string]{api.CreateStringResult("a"), api.CreateStringResult("0")}) + assert.Equal(suite.T(), initialCursor, resCursor) + assert.Equal(suite.T(), resCollection, []string{"a", "0"}) // Result contains a subset of the key res, err = client.ZAdd(key1, numberMap) @@ -5386,11 +5372,11 @@ func (suite *GlideTestSuite) TestZScan() { resCursor, resCollection, err = client.ZScan(key1, "0") assert.NoError(suite.T(), err) resultCollection := resCollection - resKeys := []api.Result[string]{} + resKeys := []string{} // 0 is returned for the cursor of the last iteration - for resCursor.Value() != "0" { - nextCursor, nextCol, err := client.ZScan(key1, resCursor.Value()) + for resCursor != "0" { + nextCursor, nextCol, err := client.ZScan(key1, resCursor) assert.NoError(suite.T(), err) assert.NotEqual(suite.T(), nextCursor, resCursor) assert.False(suite.T(), isSubset(resultCollection, nextCol)) @@ -5404,27 +5390,27 @@ func (suite *GlideTestSuite) TestZScan() { assert.NotEmpty(suite.T(), resultCollection) // Verify we got all keys and values - assert.True(suite.T(), isSubset(numMembersResult, resKeys)) + assert.True(suite.T(), isSubset(numMembers, resKeys)) // Test match pattern opts = options.NewZScanOptionsBuilder().SetMatch("*") resCursor, resCollection, err = client.ZScanWithOptions(key1, initialCursor, opts) assert.NoError(suite.T(), err) - assert.NotEqual(suite.T(), initialCursor, resCursor.Value()) + assert.NotEqual(suite.T(), initialCursor, resCursor) assert.GreaterOrEqual(suite.T(), len(resCollection), defaultCount) // test count opts = options.NewZScanOptionsBuilder().SetCount(20) resCursor, resCollection, err = client.ZScanWithOptions(key1, initialCursor, opts) assert.NoError(suite.T(), err) - assert.NotEqual(suite.T(), initialCursor, resCursor.Value()) + assert.NotEqual(suite.T(), initialCursor, resCursor) assert.GreaterOrEqual(suite.T(), len(resCollection), 20) // test count with match, returns a non-empty array opts = options.NewZScanOptionsBuilder().SetMatch("1*").SetCount(20) resCursor, resCollection, err = client.ZScanWithOptions(key1, initialCursor, opts) assert.NoError(suite.T(), err) - assert.NotEqual(suite.T(), initialCursor, resCursor.Value()) + assert.NotEqual(suite.T(), initialCursor, resCursor) assert.GreaterOrEqual(suite.T(), len(resCollection), 0) // Test NoScores option for Redis 8.0.0+ @@ -5432,13 +5418,13 @@ func (suite *GlideTestSuite) TestZScan() { opts = options.NewZScanOptionsBuilder().SetNoScores(true) resCursor, resCollection, err = client.ZScanWithOptions(key1, initialCursor, opts) assert.NoError(suite.T(), err) - cursor, err := strconv.ParseInt(resCursor.Value(), 10, 64) + cursor, err := strconv.ParseInt(resCursor, 10, 64) assert.NoError(suite.T(), err) assert.GreaterOrEqual(suite.T(), cursor, int64(0)) // Verify all fields start with "member" for _, field := range resCollection { - assert.True(suite.T(), strings.HasPrefix(field.Value(), "member")) + assert.True(suite.T(), strings.HasPrefix(field, "member")) } } diff --git a/go/integTest/test_utils.go b/go/integTest/test_utils.go index 144d019dfc..8e7b37bb8f 100644 --- a/go/integTest/test_utils.go +++ b/go/integTest/test_utils.go @@ -2,28 +2,26 @@ package integTest -import "github.com/valkey-io/valkey-glide/go/glide/api" - // check if sliceA is a subset of sliceB -func isSubset(sliceA []api.Result[string], sliceB []api.Result[string]) bool { - setB := make(map[string]struct{}) +func isSubset[T comparable](sliceA []T, sliceB []T) bool { + setB := make(map[T]struct{}) for _, v := range sliceB { - setB[v.Value()] = struct{}{} + setB[v] = struct{}{} } for _, v := range sliceA { - if _, found := setB[v.Value()]; !found { + if _, found := setB[v]; !found { return false } } return true } -func convertMapKeysAndValuesToResultList(m map[string]string) ([]api.Result[string], []api.Result[string]) { - keys := make([]api.Result[string], 0) - values := make([]api.Result[string], 0) +func convertMapKeysAndValuesToLists(m map[string]string) ([]string, []string) { + keys := make([]string, 0) + values := make([]string, 0) for key, value := range m { - keys = append(keys, api.CreateStringResult(key)) - values = append(values, api.CreateStringResult(value)) + keys = append(keys, key) + values = append(values, value) } return keys, values } From dfff4e0a4fd9d69d6d2a5af4233eaebd60f00e77 Mon Sep 17 00:00:00 2001 From: Edric Cuartero Date: Mon, 20 Jan 2025 12:26:43 +0800 Subject: [PATCH 10/21] Go implement object freq object idle object ref count command (#2958) * Implement Object Freq, Idle and RefCount Signed-off-by: EdricCua Signed-off-by: Edward Liang --- go/api/base_client.go | 83 ++++++++++++++++++++++++++++ go/api/generic_base_commands.go | 6 ++ go/integTest/shared_commands_test.go | 74 +++++++++++++++++++++++++ 3 files changed, 163 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index d132a20c4b..8ee72b4998 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -5608,3 +5608,86 @@ func (client *baseClient) ZRemRangeByScore(key string, rangeQuery options.RangeB } return handleIntResponse(result) } + +// Returns the logarithmic access frequency counter of a Valkey object stored at key. +// +// Parameters: +// +// key - The key of the object to get the logarithmic access frequency counter of. +// +// Return value: +// +// If key exists, returns the logarithmic access frequency counter of the +// object stored at key as a long. Otherwise, returns `nil`. +// +// Example: +// +// result, err := client.ObjectFreq(key) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/object-freq/ +func (client *baseClient) ObjectFreq(key string) (Result[int64], error) { + result, err := client.executeCommand(C.ObjectFreq, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleIntOrNilResponse(result) +} + +// Returns the logarithmic access frequency counter of a Valkey object stored at key. +// +// Parameters: +// +// key - The key of the object to get the logarithmic access frequency counter of. +// +// Return value: +// +// If key exists, returns the idle time in seconds. Otherwise, returns `nil`. +// +// Example: +// +// result, err := client.ObjectIdleTime(key) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/object-idletime/ +func (client *baseClient) ObjectIdleTime(key string) (Result[int64], error) { + result, err := client.executeCommand(C.ObjectIdleTime, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleIntOrNilResponse(result) +} + +// Returns the reference count of the object stored at key. +// +// Parameters: +// +// key - The key of the object to get the reference count of. +// +// Return value: +// +// If key exists, returns the reference count of the object stored at key. +// Otherwise, returns `nil`. +// +// Example: +// +// result, err := client.ObjectRefCount(key) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: 1 +// +// [valkey.io]: https://valkey.io/commands/object-refcount/ +func (client *baseClient) ObjectRefCount(key string) (Result[int64], error) { + result, err := client.executeCommand(C.ObjectRefCount, []string{key}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleIntOrNilResponse(result) +} diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index 7897c69e19..3441a26c12 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -133,4 +133,10 @@ type GenericBaseCommands interface { // // [valkey.io]: https://valkey.io/commands/dump/ Dump(key string) (Result[string], error) + + ObjectFreq(key string) (Result[int64], error) + + ObjectIdleTime(key string) (Result[int64], error) + + ObjectRefCount(key string) (Result[int64], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 184cf3e430..21feff57d2 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -6231,3 +6231,77 @@ func (suite *GlideTestSuite) TestZRemRangeByScore() { assert.IsType(suite.T(), &api.RequestError{}, err) }) } + +func (suite *GlideTestSuite) TestObjectIdleTime() { + suite.runWithDefaultClients(func(client api.BaseClient) { + defaultClient := suite.defaultClient() + key := "testKey1_" + uuid.New().String() + value := "hello" + sleepSec := int64(5) + t := suite.T() + suite.verifyOK(defaultClient.Set(key, value)) + keyValueMap := map[string]string{ + "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") + resultGet, err := defaultClient.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet.Value()) + time.Sleep(time.Duration(sleepSec) * time.Second) + resultIdleTime, err := defaultClient.ObjectIdleTime(key) + assert.Nil(t, err) + assert.GreaterOrEqual(t, resultIdleTime.Value(), sleepSec) + }) +} + +func (suite *GlideTestSuite) TestObjectRefCount() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(client.Set(key, value)) + resultGetRestoreKey, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGetRestoreKey.Value()) + resultObjectRefCount, err := client.ObjectRefCount(key) + assert.Nil(t, err) + assert.GreaterOrEqual(t, resultObjectRefCount.Value(), int64(1)) + }) +} + +func (suite *GlideTestSuite) TestObjectFreq() { + suite.runWithDefaultClients(func(client api.BaseClient) { + defaultClient := suite.defaultClient() + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(defaultClient.Set(key, value)) + keyValueMap := map[string]string{ + "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") + sleepSec := int64(5) + time.Sleep(time.Duration(sleepSec) * time.Second) + resultGet, err := defaultClient.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet.Value()) + resultGet2, err := defaultClient.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet2.Value()) + resultObjFreq, err := defaultClient.ObjectFreq(key) + assert.Nil(t, err) + assert.GreaterOrEqual(t, resultObjFreq.Value(), int64(2)) + }) +} From 20300002b8a8229c4696242c4744eed61d9f1e90 Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju <31915502+niharikabhavaraju@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:33:02 +0000 Subject: [PATCH 11/21] Go: Implement Sort, Sort ReadOnly and Sort Store commands (#2888) * Sort,Sort_RO,Sort Store commands Signed-off-by: Niharika Bhavaraju Signed-off-by: Edward Liang --- go/api/base_client.go | 55 ++++ go/api/generic_base_commands.go | 172 +++++++++++ go/api/options/sort_options.go | 131 ++++++++ go/integTest/shared_commands_test.go | 364 +++++++++++++++++++++++ go/integTest/standalone_commands_test.go | 115 +++++++ 5 files changed, 837 insertions(+) create mode 100644 go/api/options/sort_options.go diff --git a/go/api/base_client.go b/go/api/base_client.go index 8ee72b4998..0d7a0b1ed2 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -5691,3 +5691,58 @@ func (client *baseClient) ObjectRefCount(key string) (Result[int64], error) { } return handleIntOrNilResponse(result) } + +func (client *baseClient) Sort(key string) ([]Result[string], error) { + result, err := client.executeCommand(C.Sort, []string{key}) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result) +} + +func (client *baseClient) SortWithOptions(key string, options *options.SortOptions) ([]Result[string], error) { + optionArgs := options.ToArgs() + result, err := client.executeCommand(C.Sort, append([]string{key}, optionArgs...)) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result) +} + +func (client *baseClient) SortReadOnly(key string) ([]Result[string], error) { + result, err := client.executeCommand(C.SortReadOnly, []string{key}) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result) +} + +func (client *baseClient) SortReadOnlyWithOptions(key string, options *options.SortOptions) ([]Result[string], error) { + optionArgs := options.ToArgs() + result, err := client.executeCommand(C.SortReadOnly, append([]string{key}, optionArgs...)) + if err != nil { + return nil, err + } + return handleStringArrayResponse(result) +} + +func (client *baseClient) SortStore(key string, destination string) (Result[int64], error) { + result, err := client.executeCommand(C.Sort, []string{key, "STORE", destination}) + if err != nil { + return CreateNilInt64Result(), err + } + return handleIntOrNilResponse(result) +} + +func (client *baseClient) SortStoreWithOptions( + key string, + destination string, + options *options.SortOptions, +) (Result[int64], error) { + optionArgs := options.ToArgs() + result, err := client.executeCommand(C.Sort, append([]string{key, "STORE", destination}, optionArgs...)) + if err != nil { + return CreateNilInt64Result(), err + } + return handleIntOrNilResponse(result) +} diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index 3441a26c12..abf0ca17f3 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -2,6 +2,8 @@ package api +import "github.com/valkey-io/valkey-glide/go/glide/api/options" + // Supports commands and transactions for the "Generic Commands" group for standalone and cluster clients. // // See [valkey.io] for details. @@ -139,4 +141,174 @@ type GenericBaseCommands interface { ObjectIdleTime(key string) (Result[int64], error) ObjectRefCount(key string) (Result[int64], error) + + // Sorts the elements in the list, set, or sorted set at key and returns the result. + // The sort command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // To store the result into a new key, see the sortStore function. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // + // Return value: + // An Array of sorted elements. + // + // Example: + // + // result, err := client.Sort("key") + // result.Value(): [{1 false} {2 false} {3 false}] + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + Sort(key string) ([]Result[string], error) + + // Sorts the elements in the list, set, or sorted set at key and returns the result. + // The sort command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // To store the result into a new key, see the sortStore function. + // + // Note: + // In cluster mode, if `key` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. + // The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is + // supported since Valkey version 8.0. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // sortOptions - The SortOptions type. + // + // Return value: + // An Array of sorted elements. + // + // Example: + // + // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") + // result, err := client.Sort("key", options) + // result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + SortWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) + + // Sorts the elements in the list, set, or sorted set at key and stores the result in + // destination. The sort command can be used to sort elements based on + // different criteria, apply transformations on sorted elements, and store the result in a new key. + // The sort command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // To get the sort result without storing it into a key, see the sort or sortReadOnly function. + // + // Note: + // In cluster mode, if `key` and `destination` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // destination - The key where the sorted result will be stored. + // + // Return value: + // The number of elements in the sorted key stored at destination. + // + // Example: + // + // result, err := client.SortStore("key","destkey") + // result.Value(): 1 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + SortStore(key string, destination string) (Result[int64], error) + + // Sorts the elements in the list, set, or sorted set at key and stores the result in + // destination. The sort command can be used to sort elements based on + // different criteria, apply transformations on sorted elements, and store the result in a new key. + // The sort command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // To get the sort result without storing it into a key, see the sort or sortReadOnly function. + // + // Note: + // In cluster mode, if `key` and `destination` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. + // The use of SortOptions.byPattern and SortOptions.getPatterns + // in cluster mode is supported since Valkey version 8.0. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // destination - The key where the sorted result will be stored. + // sortOptions - The SortOptions type. + // + // Return value: + // The number of elements in the sorted key stored at destination. + // + // Example: + // + // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") + // result, err := client.SortStore("key","destkey",options) + // result.Value(): 1 + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + SortStoreWithOptions(key string, destination string, sortOptions *options.SortOptions) (Result[int64], error) + + // Sorts the elements in the list, set, or sorted set at key and returns the result. + // The sortReadOnly command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // This command is routed depending on the client's ReadFrom strategy. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // + // Return value: + // An Array of sorted elements. + // + // Example: + // + // result, err := client.SortReadOnly("key") + // result.Value(): [{1 false} {2 false} {3 false}] + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + SortReadOnly(key string) ([]Result[string], error) + + // Sorts the elements in the list, set, or sorted set at key and returns the result. + // The sort command can be used to sort elements based on different criteria and apply + // transformations on sorted elements. + // This command is routed depending on the client's ReadFrom strategy. + // + // Note: + // In cluster mode, if `key` map to different hash slots, the command + // will be split across these slots and executed separately for each. This means the command + // is atomic only at the slot level. If one or more slot-specific requests fail, the entire + // call will return the first encountered error, even though some requests may have succeeded + // while others did not. If this behavior impacts your application logic, consider splitting + // the request into sub-requests per slot to ensure atomicity. + // The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is + // supported since Valkey version 8.0. + // + // Parameters: + // key - The key of the list, set, or sorted set to be sorted. + // sortOptions - The SortOptions type. + // + // Return value: + // An Array of sorted elements. + // + // Example: + // + // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") + // result, err := client.SortReadOnly("key", options) + // result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] + // result.IsNil(): false + // + // [valkey.io]: https://valkey.io/commands/sort/ + SortReadOnlyWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) } diff --git a/go/api/options/sort_options.go b/go/api/options/sort_options.go new file mode 100644 index 0000000000..6ff883295c --- /dev/null +++ b/go/api/options/sort_options.go @@ -0,0 +1,131 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +package options + +import ( + "github.com/valkey-io/valkey-glide/go/glide/utils" +) + +const ( + // LIMIT subcommand string to include in the SORT and SORT_RO commands. + LIMIT_COMMAND_STRING = "LIMIT" + // ALPHA subcommand string to include in the SORT and SORT_RO commands. + ALPHA_COMMAND_STRING = "ALPHA" + // BY subcommand string to include in the SORT and SORT_RO commands. + // Supported in cluster mode since Valkey version 8.0 and above. + BY_COMMAND_STRING = "BY" + // GET subcommand string to include in the SORT and SORT_RO commands. + GET_COMMAND_STRING = "GET" +) + +// SortLimit struct represents the range of elements to retrieve +// The LIMIT argument is commonly used to specify a subset of results from the matching elements, similar to the +// LIMIT clause in SQL (e.g., `SELECT LIMIT offset, count`). +type SortLimit struct { + Offset int64 + Count int64 +} + +// OrderBy specifies the order to sort the elements. Can be ASC (ascending) or DESC(descending). +type OrderBy string + +const ( + ASC OrderBy = "ASC" + DESC OrderBy = "DESC" +) + +// SortOptions struct combines both the base options and additional sorting options +type SortOptions struct { + SortLimit *SortLimit + OrderBy OrderBy + IsAlpha bool + ByPattern string + GetPatterns []string +} + +func NewSortOptions() *SortOptions { + return &SortOptions{ + OrderBy: ASC, // Default order is ascending + IsAlpha: false, // Default is numeric sorting + } +} + +// SortLimit Limits the range of elements +// Offset is the starting position of the range, zero based. +// Count is the maximum number of elements to include in the range. +// A negative count returns all elements from the offset. +func (opts *SortOptions) SetSortLimit(offset, count int64) *SortOptions { + opts.SortLimit = &SortLimit{Offset: offset, Count: count} + return opts +} + +// OrderBy sets the order to sort by (ASC or DESC) +func (opts *SortOptions) SetOrderBy(order OrderBy) *SortOptions { + opts.OrderBy = order + return opts +} + +// IsAlpha determines whether to sort lexicographically (true) or numerically (false) +func (opts *SortOptions) SetIsAlpha(isAlpha bool) *SortOptions { + opts.IsAlpha = isAlpha + return opts +} + +// ByPattern - a pattern to sort by external keys instead of by the elements stored at the key themselves. The +// pattern should contain an asterisk (*) as a placeholder for the element values, where the value +// from the key replaces the asterisk to create the key name. For example, if key +// contains IDs of objects, byPattern can be used to sort these IDs based on an +// attribute of the objects, like their weights or timestamps. +// Supported in cluster mode since Valkey version 8.0 and above. +func (opts *SortOptions) SetByPattern(byPattern string) *SortOptions { + opts.ByPattern = byPattern + return opts +} + +// A pattern used to retrieve external keys' values, instead of the elements at key. +// The pattern should contain an asterisk (*) as a placeholder for the element values, where the +// value from key replaces the asterisk to create the key name. This +// allows the sorted elements to be transformed based on the related keys values. For example, if +// key< contains IDs of users, getPatterns can be used to retrieve +// specific attributes of these users, such as their names or email addresses. E.g., if +// getPatterns is name_*, the command will return the values of the keys +// name_<element> for each sorted element. Multiple getPatterns +// arguments can be provided to retrieve multiple attributes. The special value # can +// be used to include the actual element from key being sorted. If not provided, only +// the sorted elements themselves are returned. +// Supported in cluster mode since Valkey version 8.0 and above. +func (opts *SortOptions) AddGetPattern(getPattern string) *SortOptions { + opts.GetPatterns = append(opts.GetPatterns, getPattern) + return opts +} + +// ToArgs creates the arguments to be used in SORT and SORT_RO commands. +func (opts *SortOptions) ToArgs() []string { + var args []string + + if opts.SortLimit != nil { + args = append( + args, + LIMIT_COMMAND_STRING, + utils.IntToString(opts.SortLimit.Offset), + utils.IntToString(opts.SortLimit.Count), + ) + } + + if opts.OrderBy != "" { + args = append(args, string(opts.OrderBy)) + } + + if opts.IsAlpha { + args = append(args, ALPHA_COMMAND_STRING) + } + + if opts.ByPattern != "" { + args = append(args, BY_COMMAND_STRING, opts.ByPattern) + } + + for _, getPattern := range opts.GetPatterns { + args = append(args, GET_COMMAND_STRING, getPattern) + } + return args +} diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 21feff57d2..5a2f34050c 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3813,6 +3813,225 @@ func (suite *GlideTestSuite) TestPfCount_NoExistingKeys() { }) } +func (suite *GlideTestSuite) TestSortWithOptions_AscendingOrder() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"b", "a", "c"}) + + options := options.NewSortOptions(). + SetOrderBy(options.ASC). + SetIsAlpha(true) + + sortResult, err := client.SortWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("a"), + api.CreateStringResult("b"), + api.CreateStringResult("c"), + } + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSortWithOptions_DescendingOrder() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"b", "a", "c"}) + + options := options.NewSortOptions(). + SetOrderBy(options.DESC). + SetIsAlpha(true). + SetSortLimit(0, 3) + + sortResult, err := client.SortWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("c"), + api.CreateStringResult("b"), + api.CreateStringResult("a"), + } + + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSort_SuccessfulSort() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"3", "1", "2"}) + + sortResult, err := client.Sort(key) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("1"), + api.CreateStringResult("2"), + api.CreateStringResult("3"), + } + + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSortStore_BasicSorting() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "{listKey}" + uuid.New().String() + sortedKey := "{listKey}" + uuid.New().String() + client.LPush(key, []string{"10", "2", "5", "1", "4"}) + + result, err := client.SortStore(key, sortedKey) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), result) + assert.Equal(suite.T(), int64(5), result.Value()) + + sortedValues, err := client.LRange(sortedKey, 0, -1) + resultList := []api.Result[string]{ + api.CreateStringResult("1"), + api.CreateStringResult("2"), + api.CreateStringResult("4"), + api.CreateStringResult("5"), + api.CreateStringResult("10"), + } + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resultList, sortedValues) + }) +} + +func (suite *GlideTestSuite) TestSortStore_ErrorHandling() { + suite.runWithDefaultClients(func(client api.BaseClient) { + result, err := client.SortStore("{listKey}nonExistingKey", "{listKey}mydestinationKey") + + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), int64(0), result.Value()) + }) +} + +func (suite *GlideTestSuite) TestSortStoreWithOptions_DescendingOrder() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "{key}" + uuid.New().String() + sortedKey := "{key}" + uuid.New().String() + client.LPush(key, []string{"30", "20", "10", "40", "50"}) + + options := options.NewSortOptions().SetOrderBy(options.DESC).SetIsAlpha(false) + result, err := client.SortStoreWithOptions(key, sortedKey, options) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), result) + assert.Equal(suite.T(), int64(5), result.Value()) + + sortedValues, err := client.LRange(sortedKey, 0, -1) + resultList := []api.Result[string]{ + api.CreateStringResult("50"), + api.CreateStringResult("40"), + api.CreateStringResult("30"), + api.CreateStringResult("20"), + api.CreateStringResult("10"), + } + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resultList, sortedValues) + }) +} + +func (suite *GlideTestSuite) TestSortStoreWithOptions_AlphaSorting() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "{listKey}" + uuid.New().String() + sortedKey := "{listKey}" + uuid.New().String() + client.LPush(key, []string{"apple", "banana", "cherry", "date", "elderberry"}) + + options := options.NewSortOptions().SetIsAlpha(true) + result, err := client.SortStoreWithOptions(key, sortedKey, options) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), result) + assert.Equal(suite.T(), int64(5), result.Value()) + + sortedValues, err := client.LRange(sortedKey, 0, -1) + resultList := []api.Result[string]{ + api.CreateStringResult("apple"), + api.CreateStringResult("banana"), + api.CreateStringResult("cherry"), + api.CreateStringResult("date"), + api.CreateStringResult("elderberry"), + } + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resultList, sortedValues) + }) +} + +func (suite *GlideTestSuite) TestSortStoreWithOptions_Limit() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "{listKey}" + uuid.New().String() + sortedKey := "{listKey}" + uuid.New().String() + client.LPush(key, []string{"10", "20", "30", "40", "50"}) + + options := options.NewSortOptions().SetSortLimit(1, 3) + result, err := client.SortStoreWithOptions(key, sortedKey, options) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), result) + assert.Equal(suite.T(), int64(3), result.Value()) + + sortedValues, err := client.LRange(sortedKey, 0, -1) + resultList := []api.Result[string]{ + api.CreateStringResult("20"), + api.CreateStringResult("30"), + api.CreateStringResult("40"), + } + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resultList, sortedValues) + }) +} + +func (suite *GlideTestSuite) TestSortReadOnly_SuccessfulSort() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"3", "1", "2"}) + + sortResult, err := client.SortReadOnly(key) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("1"), + api.CreateStringResult("2"), + api.CreateStringResult("3"), + } + + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSortReadyOnlyWithOptions_DescendingOrder() { + suite.SkipIfServerVersionLowerThanBy("7.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"b", "a", "c"}) + + options := options.NewSortOptions(). + SetOrderBy(options.DESC). + SetIsAlpha(true). + SetSortLimit(0, 3) + + sortResult, err := client.SortReadOnlyWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("c"), + api.CreateStringResult("b"), + api.CreateStringResult("a"), + } + assert.Equal(suite.T(), resultList, sortResult) + }) +} + func (suite *GlideTestSuite) TestBLMove() { if suite.serverVersion < "6.2.0" { suite.T().Skip("This feature is added in version 6.2.0") @@ -6305,3 +6524,148 @@ func (suite *GlideTestSuite) TestObjectFreq() { assert.GreaterOrEqual(t, resultObjFreq.Value(), int64(2)) }) } + +func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { + suite.SkipIfServerVersionLowerThanBy("8.1.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("weight_item1", "3") + client.Set("weight_item2", "1") + client.Set("weight_item3", "2") + + options := options.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(options.ASC). + SetIsAlpha(false) + + sortResult, err := client.SortWithOptions(key, options) + + assert.Nil(suite.T(), err) + resultList := []api.Result[string]{ + api.CreateStringResult("item2"), + api.CreateStringResult("item3"), + api.CreateStringResult("item1"), + } + + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSortWithOptions_GetPatterns() { + suite.SkipIfServerVersionLowerThanBy("8.1.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("object_item1", "Object_1") + client.Set("object_item2", "Object_2") + client.Set("object_item3", "Object_3") + + options := options.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(options.ASC). + SetIsAlpha(false). + AddGetPattern("object_*") + + sortResult, err := client.SortWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("Object_2"), + api.CreateStringResult("Object_3"), + api.CreateStringResult("Object_1"), + } + + assert.Equal(suite.T(), resultList, sortResult) + }) +} + +func (suite *GlideTestSuite) TestSortWithOptions_SuccessfulSortByWeightAndGet() { + suite.SkipIfServerVersionLowerThanBy("8.1.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("weight_item1", "10") + client.Set("weight_item2", "5") + client.Set("weight_item3", "15") + + client.Set("object_item1", "Object 1") + client.Set("object_item2", "Object 2") + client.Set("object_item3", "Object 3") + + options := options.NewSortOptions(). + SetOrderBy(options.ASC). + SetIsAlpha(false). + SetByPattern("weight_*"). + AddGetPattern("object_*"). + AddGetPattern("#") + + sortResult, err := client.SortWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("Object 2"), + api.CreateStringResult("item2"), + api.CreateStringResult("Object 1"), + api.CreateStringResult("item1"), + api.CreateStringResult("Object 3"), + api.CreateStringResult("item3"), + } + + assert.Equal(suite.T(), resultList, sortResult) + + objectItem2, err := client.Get("object_item2") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 2", objectItem2.Value()) + + objectItem1, err := client.Get("object_item1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 1", objectItem1.Value()) + + objectItem3, err := client.Get("object_item3") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 3", objectItem3.Value()) + + assert.Equal(suite.T(), "item2", sortResult[1].Value()) + assert.Equal(suite.T(), "item1", sortResult[3].Value()) + assert.Equal(suite.T(), "item3", sortResult[5].Value()) + }) +} + +func (suite *GlideTestSuite) TestSortStoreWithOptions_ByPattern() { + suite.SkipIfServerVersionLowerThanBy("8.1.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "{listKey}" + uuid.New().String() + sortedKey := "{listKey}" + uuid.New().String() + client.LPush(key, []string{"a", "b", "c", "d", "e"}) + client.Set("{listKey}weight_a", "5") + client.Set("{listKey}weight_b", "2") + client.Set("{listKey}weight_c", "3") + client.Set("{listKey}weight_d", "1") + client.Set("{listKey}weight_e", "4") + + options := options.NewSortOptions().SetByPattern("{listKey}weight_*") + + result, err := client.SortStoreWithOptions(key, sortedKey, options) + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), result) + assert.Equal(suite.T(), int64(5), result.Value()) + + sortedValues, err := client.LRange(sortedKey, 0, -1) + resultList := []api.Result[string]{ + api.CreateStringResult("d"), + api.CreateStringResult("b"), + api.CreateStringResult("c"), + api.CreateStringResult("e"), + api.CreateStringResult("a"), + } + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), resultList, sortedValues) + }) +} diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 063e884a5d..3c298f1ee6 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/valkey-io/valkey-glide/go/glide/api" + "github.com/valkey-io/valkey-glide/go/glide/api/options" "github.com/stretchr/testify/assert" ) @@ -273,3 +274,117 @@ func (suite *GlideTestSuite) TestSelect_SwitchBetweenDatabases() { assert.Nil(suite.T(), err) assert.Equal(suite.T(), value2, result.Value()) } + +func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { + client := suite.defaultClient() + if suite.serverVersion < "7.0.0" { + suite.T().Skip("This feature is added in version 7") + } + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("weight_item1", "3") + client.Set("weight_item2", "1") + client.Set("weight_item3", "2") + + options := options.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(options.ASC). + SetIsAlpha(false) + + sortResult, err := client.SortReadOnlyWithOptions(key, options) + + assert.Nil(suite.T(), err) + resultList := []api.Result[string]{ + api.CreateStringResult("item2"), + api.CreateStringResult("item3"), + api.CreateStringResult("item1"), + } + assert.Equal(suite.T(), resultList, sortResult) +} + +func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_GetPatterns() { + client := suite.defaultClient() + if suite.serverVersion < "7.0.0" { + suite.T().Skip("This feature is added in version 7") + } + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("object_item1", "Object_1") + client.Set("object_item2", "Object_2") + client.Set("object_item3", "Object_3") + + options := options.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(options.ASC). + SetIsAlpha(false). + AddGetPattern("object_*") + + sortResult, err := client.SortReadOnlyWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("Object_2"), + api.CreateStringResult("Object_3"), + api.CreateStringResult("Object_1"), + } + + assert.Equal(suite.T(), resultList, sortResult) +} + +func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_SuccessfulSortByWeightAndGet() { + client := suite.defaultClient() + if suite.serverVersion < "7.0.0" { + suite.T().Skip("This feature is added in version 7") + } + key := uuid.New().String() + client.LPush(key, []string{"item1", "item2", "item3"}) + + client.Set("weight_item1", "10") + client.Set("weight_item2", "5") + client.Set("weight_item3", "15") + + client.Set("object_item1", "Object 1") + client.Set("object_item2", "Object 2") + client.Set("object_item3", "Object 3") + + options := options.NewSortOptions(). + SetOrderBy(options.ASC). + SetIsAlpha(false). + SetByPattern("weight_*"). + AddGetPattern("object_*"). + AddGetPattern("#") + + sortResult, err := client.SortReadOnlyWithOptions(key, options) + + assert.Nil(suite.T(), err) + + resultList := []api.Result[string]{ + api.CreateStringResult("Object 2"), + api.CreateStringResult("item2"), + api.CreateStringResult("Object 1"), + api.CreateStringResult("item1"), + api.CreateStringResult("Object 3"), + api.CreateStringResult("item3"), + } + + assert.Equal(suite.T(), resultList, sortResult) + + objectItem2, err := client.Get("object_item2") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 2", objectItem2.Value()) + + objectItem1, err := client.Get("object_item1") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 1", objectItem1.Value()) + + objectItem3, err := client.Get("object_item3") + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "Object 3", objectItem3.Value()) + + assert.Equal(suite.T(), "item2", sortResult[1].Value()) + assert.Equal(suite.T(), "item1", sortResult[3].Value()) + assert.Equal(suite.T(), "item3", sortResult[5].Value()) +} From 50960180baf41ec95df018f900e3cc672ff14b74 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Mon, 20 Jan 2025 10:01:01 -0800 Subject: [PATCH 12/21] Go: `XGROUP CREATE`. (#2966) * Go: `XGROUP CREATE`. Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 67 ++++++++++++++++++++++++++++ go/api/options/stream_options.go | 40 +++++++++++++++++ go/api/stream_commands.go | 4 ++ go/integTest/shared_commands_test.go | 63 +++++++++++++++++++++----- 4 files changed, 162 insertions(+), 12 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 0d7a0b1ed2..fc4b1abc35 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -5449,6 +5449,73 @@ func (client *baseClient) XPendingWithOptions( return handleXPendingDetailResponse(result) } +// Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The newly created consumer group name. +// id - Stream entry ID that specifies the last delivered entry in the stream from the new +// group’s perspective. The special ID `"$"` can be used to specify the last entry in the stream. +// +// Return value: +// +// `"OK"`. +// +// Example: +// +// ok, err := client.XGroupCreate("mystream", "mygroup", "0-0") +// if ok != "OK" || err != nil { +// // handle error +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-create/ +func (client *baseClient) XGroupCreate(key string, group string, id string) (string, error) { + return client.XGroupCreateWithOptions(key, group, id, options.NewXGroupCreateOptions()) +} + +// Creates a new consumer group uniquely identified by `groupname` for the stream stored at `key`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The newly created consumer group name. +// id - Stream entry ID that specifies the last delivered entry in the stream from the new +// group's perspective. The special ID `"$"` can be used to specify the last entry in the stream. +// opts - The options for the command. See [options.XGroupCreateOptions] for details. +// +// Return value: +// +// `"OK"`. +// +// Example: +// +// opts := options.NewXGroupCreateOptions().SetMakeStream() +// ok, err := client.XGroupCreateWithOptions("mystream", "mygroup", "0-0", opts) +// if ok != "OK" || err != nil { +// // handle error +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-create/ +func (client *baseClient) XGroupCreateWithOptions( + key string, + group string, + id string, + opts *options.XGroupCreateOptions, +) (string, error) { + optionArgs, _ := opts.ToArgs() + args := append([]string{key, group, id}, optionArgs...) + result, err := client.executeCommand(C.XGroupCreate, args) + if err != nil { + return defaultStringResponse, err + } + return handleStringResponse(result) +} + func (client *baseClient) Restore(key string, ttl int64, value string) (Result[string], error) { return client.RestoreWithOptions(key, ttl, value, NewRestoreOptionsBuilder()) } diff --git a/go/api/options/stream_options.go b/go/api/options/stream_options.go index 4507b0478c..cb27269f3b 100644 --- a/go/api/options/stream_options.go +++ b/go/api/options/stream_options.go @@ -262,3 +262,43 @@ func (xpo *XPendingOptions) ToArgs() ([]string, error) { return args, nil } + +// Optional arguments for `XGroupCreate` in [StreamCommands] +type XGroupCreateOptions struct { + mkStream bool + entriesRead int64 +} + +// Create new empty `XGroupCreateOptions` +func NewXGroupCreateOptions() *XGroupCreateOptions { + return &XGroupCreateOptions{false, -1} +} + +// Once set and if the stream doesn't exist, creates a new stream with a length of `0`. +func (xgco *XGroupCreateOptions) SetMakeStream() *XGroupCreateOptions { + xgco.mkStream = true + 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 +} + +func (xgco *XGroupCreateOptions) ToArgs() ([]string, error) { + var args []string + + // if minIdleTime is set, we need to add an `IDLE` argument along with the minIdleTime + if xgco.mkStream { + args = append(args, "MKSTREAM") + } + + if xgco.entriesRead > -1 { + args = append(args, "ENTRIESREAD", utils.IntToString(xgco.entriesRead)) + } + + return args, nil +} diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 564b8b8109..c5e32bf331 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -65,4 +65,8 @@ type StreamCommands interface { XPending(key string, group string) (XPendingSummary, error) XPendingWithOptions(key string, group string, options *options.XPendingOptions) ([]XPendingDetail, error) + + XGroupCreate(key string, group string, id string) (string, error) + + XGroupCreateWithOptions(key string, group string, id string, opts *options.XGroupCreateOptions) (string, error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 5a2f34050c..2e941aafb4 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5865,15 +5865,13 @@ func (suite *GlideTestSuite) TestXPendingFailures() { consumer1 := "consumer-1-" + uuid.New().String() invalidConsumer := "invalid-consumer-" + uuid.New().String() - command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + suite.verifyOK( + client.XGroupCreateWithOptions(key, groupName, zeroStreamId, options.NewXGroupCreateOptions().SetMakeStream()), + ) + command := []string{"XGroup", "CreateConsumer", key, groupName, consumer1} resp, err := client.CustomCommand(command) assert.NoError(suite.T(), err) - assert.Equal(suite.T(), "OK", resp.(string)) - - command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} - resp, err = client.CustomCommand(command) - assert.NoError(suite.T(), err) assert.True(suite.T(), resp.(bool)) _, err = client.XAdd(key, [][]string{{"field1", "value1"}}) @@ -6017,15 +6015,13 @@ func (suite *GlideTestSuite) TestXPendingFailures() { consumer1 := "consumer-1-" + uuid.New().String() invalidConsumer := "invalid-consumer-" + uuid.New().String() - command := []string{"XGroup", "Create", key, groupName, zeroStreamId, "MKSTREAM"} + suite.verifyOK( + client.XGroupCreateWithOptions(key, groupName, zeroStreamId, options.NewXGroupCreateOptions().SetMakeStream()), + ) + command := []string{"XGroup", "CreateConsumer", key, groupName, consumer1} resp, err := client.CustomCommand(command) assert.NoError(suite.T(), err) - assert.Equal(suite.T(), "OK", resp.Value().(string)) - - command = []string{"XGroup", "CreateConsumer", key, groupName, consumer1} - resp, err = client.CustomCommand(command) - assert.NoError(suite.T(), err) assert.True(suite.T(), resp.Value().(bool)) _, err = client.XAdd(key, [][]string{{"field1", "value1"}}) @@ -6173,6 +6169,49 @@ 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() + id := "0-1" + + // Stream not created results in error + _, err := client.XGroupCreate(key, group1, 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)) + + // ...and again results in BUSYGROUP error, because group names must be unique + _, err = client.XGroupCreate(key, group1, id) + assert.ErrorContains(suite.T(), err, "BUSYGROUP") + assert.IsType(suite.T(), &api.RequestError{}, err) + + // TODO add XGroupDestroy tests there + + // 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)) + } else { + _, err = client.XGroupCreateWithOptions(key, group2, id, opts) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + } + + // key is not a stream + key = uuid.NewString() + suite.verifyOK(client.Set(key, id)) + _, err = client.XGroupCreate(key, group1, id) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} + func (suite *GlideTestSuite) TestObjectEncoding() { suite.runWithDefaultClients(func(client api.BaseClient) { // Test 1: Check object encoding for embstr From c37ca6791fc1b49731dc5623daa7f8a552bbc58d Mon Sep 17 00:00:00 2001 From: Joseph Brinkman Date: Mon, 20 Jan 2025 13:50:43 -0500 Subject: [PATCH 13/21] Go: Add stream commands XGroupCreateConsumer/XGroupDelConsumer (#2975) * go xGroupCreateConsumer and XGroupDelConsumer Signed-off-by: jbrinkman Co-authored-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 70 +++++++++++++++++ go/api/stream_commands.go | 4 + go/integTest/shared_commands_test.go | 112 +++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index fc4b1abc35..b56b60709b 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -5813,3 +5813,73 @@ func (client *baseClient) SortStoreWithOptions( } return handleIntOrNilResponse(result) } + +// XGroupCreateConsumer creates a consumer named `consumer` in 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. +// consumer - The newly created consumer. +// +// Return value: +// +// Returns `true` if the consumer is created. Otherwise, returns `false`. +// +// Example: +// +// //Creates the consumer "myconsumer" in consumer group "mygroup" +// success, err := client.xgroupCreateConsumer("mystream", "mygroup", "myconsumer") +// if err == nil && success { +// fmt.Println("Consumer created") +// } +// +// [valkey.io]: https://valkey.io/commands/xgroup-createconsumer/ +func (client *baseClient) XGroupCreateConsumer( + key string, + group string, + consumer string, +) (bool, error) { + result, err := client.executeCommand(C.XGroupCreateConsumer, []string{key, group, consumer}) + if err != nil { + return false, err + } + return handleBoolResponse(result) +} + +// XGroupDelConsumer deletes a consumer named `consumer` in the consumer group `group`. +// +// See [valkey.io] for details. +// +// Parameters: +// +// key - The key of the stream. +// group - The consumer group name. +// consumer - The consumer to delete. +// +// Returns the number of pending messages the `consumer` had before it was deleted. +// +// Example: +// +// // Deletes the consumer "myconsumer" in consumer group "mygroup" +// pendingMsgCount, err := client.XGroupDelConsumer("mystream", "mygroup", "myconsumer") +// if err != nil { +// // handle error +// } +// fmt.Printf("Consumer 'myconsumer' had %d pending messages unclaimed.\n", pendingMsgCount) +// +// [valkey.io]: https://valkey.io/commands/xgroup-delconsumer/ +func (client *baseClient) XGroupDelConsumer( + key string, + group string, + consumer string, +) (int64, error) { + result, err := client.executeCommand(C.XGroupDelConsumer, []string{key, group, consumer}) + if err != nil { + return defaultIntResponse, err + } + return handleIntResponse(result) +} diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index c5e32bf331..49b2ab7385 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -69,4 +69,8 @@ type StreamCommands interface { XGroupCreate(key string, group string, id string) (string, error) XGroupCreateWithOptions(key string, group string, id string, opts *options.XGroupCreateOptions) (string, error) + + XGroupCreateConsumer(key string, group string, consumer string) (bool, error) + + XGroupDelConsumer(key string, group string, consumer string) (int64, error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 2e941aafb4..28421c428e 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -6708,3 +6708,115 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_ByPattern() { assert.Equal(suite.T(), resultList, sortedValues) }) } + +func (suite *GlideTestSuite) TestXGroupStreamCommands() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + stringKey := uuid.New().String() + groupName := "group" + uuid.New().String() + zeroStreamId := "0" + consumerName := "consumer-" + uuid.New().String() + + sendWithCustomCommand( + suite, + client, + []string{"xgroup", "create", key, groupName, zeroStreamId, "MKSTREAM"}, + "Can't send XGROUP CREATE as a custom command", + ) + respBool, err := client.XGroupCreateConsumer(key, groupName, consumerName) + assert.NoError(suite.T(), err) + assert.True(suite.T(), respBool) + + // create a consumer for a group that doesn't exist should result in a NOGROUP error + _, err = client.XGroupCreateConsumer(key, "non-existent-group", consumerName) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + assert.True(suite.T(), strings.Contains(err.Error(), "NOGROUP")) + + // create consumer that already exists should return false + respBool, err = client.XGroupCreateConsumer(key, groupName, consumerName) + assert.NoError(suite.T(), err) + assert.False(suite.T(), respBool) + + // Delete a consumer that hasn't been created should return 0 + respInt64, err := client.XGroupDelConsumer(key, groupName, "non-existent-consumer") + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(0), respInt64) + + // Add two stream entries + streamId1, err := client.XAdd(key, [][]string{{"field1", "value1"}}) + assert.NoError(suite.T(), err) + streamId2, err := client.XAdd(key, [][]string{{"field2", "value2"}}) + assert.NoError(suite.T(), err) + + // read the stream for the consumer and mark messages as pending + expectedGroup := map[string]map[string][][]string{ + key: {streamId1.Value(): {{"field1", "value1"}}, streamId2.Value(): {{"field2", "value2"}}}, + } + actualGroup, err := client.XReadGroup(groupName, consumerName, map[string]string{key: ">"}) + assert.NoError(suite.T(), err) + assert.True(suite.T(), reflect.DeepEqual(expectedGroup, actualGroup), + "Expected and actual results do not match", + ) + + // delete one of the streams using XDel + respInt64, err = client.XDel(key, []string{streamId1.Value()}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), respInt64) + + // xreadgroup should return one empty stream and one non-empty stream + resp, err := client.XReadGroup(groupName, consumerName, map[string]string{key: zeroStreamId}) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), map[string]map[string][][]string{ + key: { + streamId1.Value(): nil, + streamId2.Value(): {{"field2", "value2"}}, + }, + }, resp) + + // add a new stream entry + streamId3, err := client.XAdd(key, [][]string{{"field3", "value3"}}) + assert.NoError(suite.T(), err) + 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") + + // 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") + + // 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") + + // Delete the consumer group and expect 1 pending message + respInt64, err = client.XGroupDelConsumer(key, groupName, consumerName) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), int64(1), respInt64) + + // Set a string key, and expect an error when you try to create or delete a consumer group + _, err = client.Set(stringKey, "test") + assert.NoError(suite.T(), err) + _, err = client.XGroupCreateConsumer(stringKey, groupName, consumerName) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + + _, err = client.XGroupDelConsumer(stringKey, groupName, consumerName) + assert.Error(suite.T(), err) + assert.IsType(suite.T(), &api.RequestError{}, err) + }) +} From 9b22bb3e8ad412c404f32fc693fb9237f25007b2 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Mon, 20 Jan 2025 11:23:53 -0800 Subject: [PATCH 14/21] Go: Fix return types, part 6 (#2965) * Fix return types. Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 391 +++++++++++++++++++++------ go/api/generic_base_commands.go | 248 +---------------- go/api/hash_commands.go | 4 +- go/api/list_commands.go | 15 +- go/api/response_handlers.go | 50 ++-- go/api/sorted_set_commands.go | 2 +- go/integTest/shared_commands_test.go | 355 +++++------------------- 7 files changed, 433 insertions(+), 632 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index b56b60709b..d21aab2d84 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -469,7 +469,7 @@ func (client *Command) MGet(keys []string) ([]Result[string], error) { return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } // Increments the number stored at key by one. If key does not exist, it is set to 0 before performing the operation. @@ -917,7 +917,7 @@ func (client *Command) HMGet(key string, fields []string) ([]Result[string], err return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } // HSet sets the specified fields to their respective values in the hash stored at key. @@ -1055,18 +1055,15 @@ func (client *Command) HLen(key string) (int64, error) { // // Return value: // -// A slice of Result[string]s containing all the values in the hash, or an empty slice when key does not exist. +// A slice containing all the values in the hash, or an empty slice when key does not exist. // // For example: // // values, err := client.HVals("myHash") -// // value1 equals api.CreateStringResult("value1") -// // value2 equals api.CreateStringResult("value2") -// // value3 equals api.CreateStringResult("value3") -// // values equals []api.Result[string]{value1, value2, value3} +// values: []string{"value1", "value2", "value3"} // // [valkey.io]: https://valkey.io/commands/hvals/ -func (client *Command) HVals(key string) ([]Result[string], error) { +func (client *baseClient) HVals(key string) ([]string, error) { result, err := client.executeCommand(C.HVals, []string{key}) if err != nil { return nil, err @@ -1118,17 +1115,15 @@ func (client *Command) HExists(key string, field string) (bool, error) { // // Return value: // -// A slice of Result[string]s containing all the field names in the hash, or an empty slice when key does not exist. +// A slice containing all the field names in the hash, or an empty slice when key does not exist. // // For example: // // names, err := client.HKeys("my_hash") -// // field1 equals api.CreateStringResult("field_1") -// // field2 equals api.CreateStringResult("field_2") -// // names equals []api.Result[string]{field1, field2} +// names: []string{"field1", "field2"} // // [valkey.io]: https://valkey.io/commands/hkeys/ -func (client *Command) HKeys(key string) ([]Result[string], error) { +func (client *baseClient) HKeys(key string) ([]string, error) { result, err := client.executeCommand(C.HKeys, []string{key}) if err != nil { return nil, err @@ -1396,13 +1391,13 @@ func (client *Command) LPop(key string) (Result[string], error) { // result: nil // // [valkey.io]: https://valkey.io/commands/lpop/ -func (client *Command) LPopCount(key string, count int64) ([]Result[string], error) { +func (client *baseClient) LPopCount(key string, count int64) ([]string, error) { result, err := client.executeCommand(C.LPop, []string{key, utils.IntToString(count)}) if err != nil { return nil, err } - return handleStringArrayOrNullResponse(result) + return handleStringArrayOrNilResponse(result) } // Returns the index of the first occurrence of element inside the list specified by key. If no match is found, @@ -1485,12 +1480,12 @@ func (client *Command) LPosWithOptions(key string, element string, options *LPos // // For example: // -// result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// _, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) // result, err := client.LPosCount("my_list", "e", int64(3)) -// result: []api.Result[int64]{api.CreateInt64Result(4), api.CreateInt64Result(5), api.CreateInt64Result(6)} +// result: []int64{ 4, 5, 6 } // // [valkey.io]: https://valkey.io/commands/lpos/ -func (client *Command) LPosCount(key string, element string, count int64) ([]Result[int64], error) { +func (client *baseClient) LPosCount(key string, element string, count int64) ([]int64, error) { result, err := client.executeCommand(C.LPos, []string{key, element, CountKeyword, utils.IntToString(count)}) if err != nil { return nil, err @@ -1516,17 +1511,17 @@ func (client *Command) LPosCount(key string, element string, count int64) ([]Res // An array that holds the indices of the matching elements within the list. // // For example: -// 1. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// 1. _, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) // result, err := client.LPosWithOptions("my_list", "e", int64(1), api.NewLPosOptionsBuilder().SetRank(2)) -// result: []api.Result[int64]{api.CreateInt64Result(5)} -// 2. result, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) +// result: []int64{ 5 } +// 2. _, err := client.RPush("my_list", []string{"a", "b", "c", "d", "e", "e", "e"}) // result, err := client.LPosWithOptions( // "my_list", // "e", // int64(3), // api.NewLPosOptionsBuilder().SetRank(2).SetMaxLen(1000), // ) -// result: []api.Result[int64]{api.CreateInt64Result(5), api.CreateInt64Result(6)} +// result: []int64{ 5, 6 } // // [valkey.io]: https://valkey.io/commands/lpos/ func (client *Command) LPosCountWithOptions( @@ -1534,7 +1529,7 @@ func (client *Command) LPosCountWithOptions( element string, count int64, options *LPosOptions, -) ([]Result[int64], error) { +) ([]int64, error) { result, err := client.executeCommand( C.LPos, append([]string{key, element, CountKeyword, utils.IntToString(count)}, options.toArgs()...), @@ -2263,16 +2258,14 @@ func (client *Command) SMove(source string, destination string, member string) ( // // For example: // 1. result, err := client.LRange("my_list", 0, 2) -// -// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2"), -// api.CreateStringResult("value3")} +// result: []string{ "value1", "value2", "value3" } // 2. result, err := client.LRange("my_list", -2, -1) -// result: []api.Result[string]{api.CreateStringResult("value2"), api.CreateStringResult("value3")} +// result: []string{ "value2", "value3" } // 3. result, err := client.LRange("non_existent_key", 0, 2) -// result: []api.Result[string]{} +// result: []string{} // // [valkey.io]: https://valkey.io/commands/lrange/ -func (client *Command) LRange(key string, start int64, end int64) ([]Result[string], error) { +func (client *baseClient) LRange(key string, start int64, end int64) ([]string, error) { result, err := client.executeCommand(C.LRange, []string{key, utils.IntToString(start), utils.IntToString(end)}) if err != nil { return nil, err @@ -2467,13 +2460,13 @@ func (client *Command) RPop(key string) (Result[string], error) { // result: nil // // [valkey.io]: https://valkey.io/commands/rpop/ -func (client *Command) RPopCount(key string, count int64) ([]Result[string], error) { +func (client *baseClient) RPopCount(key string, count int64) ([]string, error) { result, err := client.executeCommand(C.RPop, []string{key, utils.IntToString(count)}) if err != nil { return nil, err } - return handleStringArrayOrNullResponse(result) + return handleStringArrayOrNilResponse(result) } // Inserts element in the list at key either before or after the pivot. @@ -2539,24 +2532,24 @@ func (client *Command) LInsert( // // Return value: // -// A two-element array of Result[string] containing the key from which the element was popped and the value of the popped +// A two-element array containing the key from which the element was popped and the value of the popped // element, formatted as [key, value]. -// If no element could be popped and the timeout expired, returns nil. +// If no element could be popped and the timeout expired, returns `nil`. // // For example: // // result, err := client.BLPop("list1", "list2", 0.5) -// result: []api.Result[string]{api.CreateStringResult("list1"), api.CreateStringResult("element")} +// result: []string{ "list1", "element" } // // [valkey.io]: https://valkey.io/commands/blpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BLPop(keys []string, timeoutSecs float64) ([]Result[string], error) { +func (client *baseClient) BLPop(keys []string, timeoutSecs float64) ([]string, error) { result, err := client.executeCommand(C.BLPop, append(keys, utils.FloatToString(timeoutSecs))) if err != nil { return nil, err } - return handleStringArrayOrNullResponse(result) + return handleStringArrayOrNilResponse(result) } // Pops an element from the tail of the first list that is non-empty, with the given keys being checked in the order that @@ -2576,24 +2569,24 @@ func (client *Command) BLPop(keys []string, timeoutSecs float64) ([]Result[strin // // Return value: // -// A two-element array of Result[string] containing the key from which the element was popped and the value of the popped +// A two-element array containing the key from which the element was popped and the value of the popped // element, formatted as [key, value]. -// If no element could be popped and the timeoutSecs expired, returns nil. +// If no element could be popped and the timeoutSecs expired, returns `nil`. // // For example: // // result, err := client.BRPop("list1", "list2", 0.5) -// result: []api.Result[string]{api.CreateStringResult("list1"), api.CreateStringResult("element")} +// result: []string{ "list1", "element" } // // [valkey.io]: https://valkey.io/commands/brpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BRPop(keys []string, timeoutSecs float64) ([]Result[string], error) { +func (client *baseClient) BRPop(keys []string, timeoutSecs float64) ([]string, error) { result, err := client.executeCommand(C.BRPop, append(keys, utils.FloatToString(timeoutSecs))) if err != nil { return nil, err } - return handleStringArrayOrNullResponse(result) + return handleStringArrayOrNilResponse(result) } // Inserts all the specified values at the tail of the list stored at key, only if key exists and holds a list. If key is @@ -2938,9 +2931,8 @@ func (client *Command) LSet(key string, index int64, element string) (string, er // result.Value(): "one" // updatedList1, err: client.LRange("my_list1", int64(0), int64(-1)) // updatedList2, err: client.LRange("my_list2", int64(0), int64(-1)) -// updatedList1: []api.Result[string]{api.CreateStringResult("two")} -// updatedList2: []api.Result[string]{api.CreateStringResult("one"), api.CreateStringResult("three"), -// api.CreateStringResult("four")} +// updatedList1: []string{ "two" } +// updatedList2: []string{ "one", "three", "four" } // // [valkey.io]: https://valkey.io/commands/lmove/ func (client *Command) LMove( @@ -3001,9 +2993,8 @@ func (client *Command) LMove( // result.Value(): "one" // updatedList1, err: client.LRange("my_list1", int64(0), int64(-1)) // updatedList2, err: client.LRange("my_list2", int64(0), int64(-1)) -// updatedList1: []api.Result[string]{api.CreateStringResult("two")} -// updatedList2: []api.Result[string]{api.CreateStringResult("one"), api.CreateStringResult("three"), -// api.CreateStringResult("four")} +// updatedList1: []string{ "two" } +// updatedList2: []string{ "one", "three", "four" } // // [valkey.io]: https://valkey.io/commands/blmove/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands @@ -3631,7 +3622,7 @@ func (client *Command) Unlink(keys []string) (int64, error) { // // Return value: // -// If the key exists, the type of the stored value is returned. Otherwise, a none" string is returned. +// If the key exists, the type of the stored value is returned. Otherwise, a "none" string is returned. // // Example: // @@ -3639,15 +3630,15 @@ func (client *Command) Unlink(keys []string) (int64, error) { // if err != nil { // // handle error // } -// fmt.Println(result.Value()) // Output: string +// fmt.Println(result) // Output: string // // [valkey.io]: Https://valkey.io/commands/type/ -func (client *Command) Type(key string) (Result[string], error) { +func (client *baseClient) Type(key string) (string, error) { result, err := client.executeCommand(C.Type, []string{key}) if err != nil { - return CreateNilStringResult(), err + return defaultStringResponse, err } - return handleStringOrNilResponse(result) + return handleStringResponse(result) } // Alters the last access time of a key(s). A key is ignored if it does not exist. @@ -3709,15 +3700,15 @@ func (client *Command) Touch(keys []string) (int64, error) { // if err != nil { // // handle error // } -// fmt.Println(result.Value()) // Output: OK +// fmt.Println(result) // Output: OK // // [valkey.io]: https://valkey.io/commands/rename/ -func (client *Command) Rename(key string, newKey string) (Result[string], error) { +func (client *baseClient) Rename(key string, newKey string) (string, error) { result, err := client.executeCommand(C.Rename, []string{key, newKey}) if err != nil { - return CreateNilStringResult(), err + return defaultStringResponse, err } - return handleStringOrNilResponse(result) + return handleStringResponse(result) } // Renames key to newkey if newKey does not yet exist. @@ -4593,16 +4584,15 @@ func (client *Command) BZPopMin(keys []string, timeoutSecs float64) (Result[KeyW // result, err := client.ZRange("my_sorted_set", options.NewRangeByIndexQuery(0, -1)) // // // Retrieve members within a score range in descending order -// -// query := options.NewRangeByScoreQuery(options.NewScoreBoundary(3, false), -// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). -// -// .SetReverse() +// query := options.NewRangeByScoreQuery( +// options.NewScoreBoundary(3, false), +// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). +// SetReverse() // result, err := client.ZRange("my_sorted_set", query) // // `result` contains members which have scores within the range of negative infinity to 3, in descending order // // [valkey.io]: https://valkey.io/commands/zrange/ -func (client *Command) ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error) { +func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([]string, error) { args := make([]string, 0, 10) args = append(args, key) args = append(args, rangeQuery.ToArgs()...) @@ -4637,10 +4627,9 @@ func (client *Command) ZRange(key string, rangeQuery options.ZRangeQuery) ([]Res // result, err := client.ZRangeWithScores("my_sorted_set", options.NewRangeByIndexQuery(0, -1)) // // // Retrieve members within a score range in descending order -// -// query := options.NewRangeByScoreQuery(options.NewScoreBoundary(3, false), -// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). -// +// query := options.NewRangeByScoreQuery( +// options.NewScoreBoundary(3, false), +// options.NewInfiniteScoreBoundary(options.NegativeInfinity)). // SetReverse() // result, err := client.ZRangeWithScores("my_sorted_set", query) // // `result` contains members with scores within the range of negative infinity to 3, in descending order @@ -5516,10 +5505,56 @@ func (client *baseClient) XGroupCreateWithOptions( return handleStringResponse(result) } +// Create a key associated with a value that is obtained by +// deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). +// +// Parameters: +// +// key - The key to create. +// ttl - The expiry time (in milliseconds). If 0, the key will persist. +// value - The serialized value to deserialize and assign to key. +// +// Return value: +// +// Return OK if successfully create a key with a value . +// +// Example: +// result, err := client.Restore("key",ttl, value) +// +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: OK +// +// [valkey.io]: https://valkey.io/commands/restore/ func (client *baseClient) Restore(key string, ttl int64, value string) (Result[string], error) { return client.RestoreWithOptions(key, ttl, value, NewRestoreOptionsBuilder()) } +// Create a key associated with a value that is obtained by +// deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). +// +// Parameters: +// +// key - The key to create. +// ttl - The expiry time (in milliseconds). If 0, the key will persist. +// value - The serialized value to deserialize and assign to key. +// restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency +// +// Return value: +// +// Return OK if successfully create a key with a value. +// +// Example: +// restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) +// resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) +// +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: OK +// +// [valkey.io]: https://valkey.io/commands/restore/ func (client *baseClient) RestoreWithOptions(key string, ttl int64, value string, options *RestoreOptions, ) (Result[string], error) { @@ -5537,6 +5572,26 @@ func (client *baseClient) RestoreWithOptions(key string, ttl int64, return handleStringOrNilResponse(result) } +// Serialize the value stored at key in a Valkey-specific format and return it to the user. +// +// Parameters: +// +// The key to serialize. +// +// Return value: +// +// The serialized value of the data stored at key +// If key does not exist, null will be returned. +// +// Example: +// +// result, err := client.Dump([]string{"key"}) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: (Serialized Value) +// +// [valkey.io]: https://valkey.io/commands/dump/ func (client *baseClient) Dump(key string) (Result[string], error) { result, err := client.executeCommand(C.Dump, []string{key}) if err != nil { @@ -5545,6 +5600,30 @@ func (client *baseClient) Dump(key string) (Result[string], error) { return handleStringOrNilResponse(result) } +// Returns the internal encoding for the Valkey object stored at key. +// +// Note: +// +// When in cluster mode, both key and newkey must map to the same hash slot. +// +// Parameters: +// +// The key of the object to get the internal encoding of. +// +// Return value: +// +// If key exists, returns the internal encoding of the object stored at +// key as a String. Otherwise, returns null. +// +// Example: +// result, err := client.ObjectEncoding("mykeyRenamenx") +// +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: embstr +// +// [valkey.io]: https://valkey.io/commands/object-encoding/ func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { result, err := client.executeCommand(C.ObjectEncoding, []string{key}) if err != nil { @@ -5759,59 +5838,219 @@ func (client *baseClient) ObjectRefCount(key string) (Result[int64], error) { return handleIntOrNilResponse(result) } +// Sorts the elements in the list, set, or sorted set at key and returns the result. +// The sort command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// To store the result into a new key, see the sortStore function. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// +// Return value: +// An Array of sorted elements. +// +// Example: +// +// result, err := client.Sort("key") +// result.Value(): [{1 false} {2 false} {3 false}] +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/sort/ func (client *baseClient) Sort(key string) ([]Result[string], error) { result, err := client.executeCommand(C.Sort, []string{key}) if err != nil { return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } +// Sorts the elements in the list, set, or sorted set at key and returns the result. +// The sort command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// To store the result into a new key, see the sortStore function. +// +// Note: +// +// In cluster mode, if `key` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is +// supported since Valkey version 8.0. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// sortOptions - The SortOptions type. +// +// Return value: +// An Array of sorted elements. +// +// Example: +// +// options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") +// result, err := client.Sort("key", options) +// result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/sort/ func (client *baseClient) SortWithOptions(key string, options *options.SortOptions) ([]Result[string], error) { optionArgs := options.ToArgs() result, err := client.executeCommand(C.Sort, append([]string{key}, optionArgs...)) if err != nil { return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } +// Sorts the elements in the list, set, or sorted set at key and returns the result. +// The sortReadOnly command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// This command is routed depending on the client's ReadFrom strategy. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// +// Return value: +// An Array of sorted elements. +// +// Example: +// +// result, err := client.SortReadOnly("key") +// result.Value(): [{1 false} {2 false} {3 false}] +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/sort/ func (client *baseClient) SortReadOnly(key string) ([]Result[string], error) { result, err := client.executeCommand(C.SortReadOnly, []string{key}) if err != nil { return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } +// Sorts the elements in the list, set, or sorted set at key and returns the result. +// The sort command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// This command is routed depending on the client's ReadFrom strategy. +// +// Note: +// +// In cluster mode, if `key` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is +// supported since Valkey version 8.0. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// sortOptions - The SortOptions type. +// +// Return value: +// An Array of sorted elements. +// +// Example: +// +// options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") +// result, err := client.SortReadOnly("key", options) +// result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] +// result.IsNil(): false +// +// [valkey.io]: https://valkey.io/commands/sort/ func (client *baseClient) SortReadOnlyWithOptions(key string, options *options.SortOptions) ([]Result[string], error) { optionArgs := options.ToArgs() result, err := client.executeCommand(C.SortReadOnly, append([]string{key}, optionArgs...)) if err != nil { return nil, err } - return handleStringArrayResponse(result) + return handleStringOrNilArrayResponse(result) } -func (client *baseClient) SortStore(key string, destination string) (Result[int64], error) { +// Sorts the elements in the list, set, or sorted set at key and stores the result in +// destination. The sort command can be used to sort elements based on +// different criteria, apply transformations on sorted elements, and store the result in a new key. +// The sort command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// To get the sort result without storing it into a key, see the sort or sortReadOnly function. +// +// Note: +// +// In cluster mode, if `key` and `destination` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// destination - The key where the sorted result will be stored. +// +// Return value: +// The number of elements in the sorted key stored at destination. +// +// Example: +// +// result, err := client.SortStore("key","destkey") +// result: 1 +// +// [valkey.io]: https://valkey.io/commands/sort/ +func (client *baseClient) SortStore(key string, destination string) (int64, error) { result, err := client.executeCommand(C.Sort, []string{key, "STORE", destination}) if err != nil { - return CreateNilInt64Result(), err + return defaultIntResponse, err } - return handleIntOrNilResponse(result) + return handleIntResponse(result) } +// Sorts the elements in the list, set, or sorted set at key and stores the result in +// destination. The sort command can be used to sort elements based on +// different criteria, apply transformations on sorted elements, and store the result in a new key. +// The sort command can be used to sort elements based on different criteria and apply +// transformations on sorted elements. +// To get the sort result without storing it into a key, see the sort or sortReadOnly function. +// +// Note: +// +// In cluster mode, if `key` and `destination` map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. +// The use of SortOptions.byPattern and SortOptions.getPatterns +// in cluster mode is supported since Valkey version 8.0. +// +// Parameters: +// key - The key of the list, set, or sorted set to be sorted. +// destination - The key where the sorted result will be stored. +// sortOptions - The SortOptions type. +// +// Return value: +// The number of elements in the sorted key stored at destination. +// +// Example: +// +// options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") +// result, err := client.SortStore("key","destkey",options) +// result: 1 +// +// [valkey.io]: https://valkey.io/commands/sort/ func (client *baseClient) SortStoreWithOptions( key string, destination string, options *options.SortOptions, -) (Result[int64], error) { +) (int64, error) { optionArgs := options.ToArgs() result, err := client.executeCommand(C.Sort, append([]string{key, "STORE", destination}, optionArgs...)) if err != nil { - return CreateNilInt64Result(), err + return defaultIntResponse, err } - return handleIntOrNilResponse(result) + return handleIntResponse(result) } // XGroupCreateConsumer creates a consumer named `consumer` in the consumer group `group` for the diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index abf0ca17f3..9b6eaef168 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -10,7 +10,6 @@ import "github.com/valkey-io/valkey-glide/go/glide/api/options" // // [valkey.io]: https://valkey.io/commands/?group=Generic type GenericBaseCommands interface { - Del(keys []string) (int64, error) Exists(keys []string) (int64, error) @@ -43,97 +42,20 @@ type GenericBaseCommands interface { Touch(keys []string) (int64, error) - Type(key string) (Result[string], error) + Type(key string) (string, error) - Rename(key string, newKey string) (Result[string], error) + Rename(key string, newKey string) (string, error) Renamenx(key string, newKey string) (bool, error) Persist(key string) (bool, error) - // Create a key associated with a value that is obtained by - // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). - // - // Parameters: - // key - The key to create. - // ttl - The expiry time (in milliseconds). If 0, the key will persist. - // value - The serialized value to deserialize and assign to key. - // - // Return value: - // Return OK if successfully create a key with a value . - // - // Example: - // result, err := client.Restore("key",ttl, value) - // if err != nil { - // // handle error - // } - // fmt.Println(result.Value()) // Output: OK - // - // [valkey.io]: https://valkey.io/commands/restore/ Restore(key string, ttl int64, value string) (Result[string], error) - // Create a key associated with a value that is obtained by - // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). - // - // Parameters: - // key - The key to create. - // ttl - The expiry time (in milliseconds). If 0, the key will persist. - // value - The serialized value to deserialize and assign to key. - // restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency - // - // Return value: - // Return OK if successfully create a key with a value. - // - // Example: - // restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) - // resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) - // if err != nil { - // // handle error - // } - // fmt.Println(result.Value()) // Output: OK - // - // [valkey.io]: https://valkey.io/commands/restore/ RestoreWithOptions(key string, ttl int64, value string, option *RestoreOptions) (Result[string], error) - // Returns the internal encoding for the Valkey object stored at key. - // - // Note: - // When in cluster mode, both key and newkey must map to the same hash slot. - // - // Parameters: - // The key of the object to get the internal encoding of. - // - // Return value: - // If key exists, returns the internal encoding of the object stored at - // key as a String. Otherwise, returns null. - // - // Example: - // result, err := client.ObjectEncoding("mykeyRenamenx") - // if err != nil { - // // handle error - // } - // fmt.Println(result.Value()) // Output: embstr - // - // [valkey.io]: https://valkey.io/commands/object-encoding/ ObjectEncoding(key string) (Result[string], error) - // Serialize the value stored at key in a Valkey-specific format and return it to the user. - // - // Parameters: - // The key to serialize. - // - // Return value: - // The serialized value of the data stored at key - // If key does not exist, null will be returned. - // - // Example: - // result, err := client.Dump([]string{"key"}) - // if err != nil { - // // handle error - // } - // fmt.Println(result.Value()) // Output: (Serialized Value) - // - // [valkey.io]: https://valkey.io/commands/dump/ Dump(key string) (Result[string], error) ObjectFreq(key string) (Result[int64], error) @@ -142,173 +64,15 @@ type GenericBaseCommands interface { ObjectRefCount(key string) (Result[int64], error) - // Sorts the elements in the list, set, or sorted set at key and returns the result. - // The sort command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // To store the result into a new key, see the sortStore function. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // - // Return value: - // An Array of sorted elements. - // - // Example: - // - // result, err := client.Sort("key") - // result.Value(): [{1 false} {2 false} {3 false}] - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ Sort(key string) ([]Result[string], error) - // Sorts the elements in the list, set, or sorted set at key and returns the result. - // The sort command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // To store the result into a new key, see the sortStore function. - // - // Note: - // In cluster mode, if `key` map to different hash slots, the command - // will be split across these slots and executed separately for each. This means the command - // is atomic only at the slot level. If one or more slot-specific requests fail, the entire - // call will return the first encountered error, even though some requests may have succeeded - // while others did not. If this behavior impacts your application logic, consider splitting - // the request into sub-requests per slot to ensure atomicity. - // The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is - // supported since Valkey version 8.0. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // sortOptions - The SortOptions type. - // - // Return value: - // An Array of sorted elements. - // - // Example: - // - // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") - // result, err := client.Sort("key", options) - // result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ SortWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) - // Sorts the elements in the list, set, or sorted set at key and stores the result in - // destination. The sort command can be used to sort elements based on - // different criteria, apply transformations on sorted elements, and store the result in a new key. - // The sort command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // To get the sort result without storing it into a key, see the sort or sortReadOnly function. - // - // Note: - // In cluster mode, if `key` and `destination` map to different hash slots, the command - // will be split across these slots and executed separately for each. This means the command - // is atomic only at the slot level. If one or more slot-specific requests fail, the entire - // call will return the first encountered error, even though some requests may have succeeded - // while others did not. If this behavior impacts your application logic, consider splitting - // the request into sub-requests per slot to ensure atomicity. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // destination - The key where the sorted result will be stored. - // - // Return value: - // The number of elements in the sorted key stored at destination. - // - // Example: - // - // result, err := client.SortStore("key","destkey") - // result.Value(): 1 - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ - SortStore(key string, destination string) (Result[int64], error) - - // Sorts the elements in the list, set, or sorted set at key and stores the result in - // destination. The sort command can be used to sort elements based on - // different criteria, apply transformations on sorted elements, and store the result in a new key. - // The sort command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // To get the sort result without storing it into a key, see the sort or sortReadOnly function. - // - // Note: - // In cluster mode, if `key` and `destination` map to different hash slots, the command - // will be split across these slots and executed separately for each. This means the command - // is atomic only at the slot level. If one or more slot-specific requests fail, the entire - // call will return the first encountered error, even though some requests may have succeeded - // while others did not. If this behavior impacts your application logic, consider splitting - // the request into sub-requests per slot to ensure atomicity. - // The use of SortOptions.byPattern and SortOptions.getPatterns - // in cluster mode is supported since Valkey version 8.0. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // destination - The key where the sorted result will be stored. - // sortOptions - The SortOptions type. - // - // Return value: - // The number of elements in the sorted key stored at destination. - // - // Example: - // - // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") - // result, err := client.SortStore("key","destkey",options) - // result.Value(): 1 - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ - SortStoreWithOptions(key string, destination string, sortOptions *options.SortOptions) (Result[int64], error) - - // Sorts the elements in the list, set, or sorted set at key and returns the result. - // The sortReadOnly command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // This command is routed depending on the client's ReadFrom strategy. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // - // Return value: - // An Array of sorted elements. - // - // Example: - // - // result, err := client.SortReadOnly("key") - // result.Value(): [{1 false} {2 false} {3 false}] - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ + SortStore(key string, destination string) (int64, error) + + SortStoreWithOptions(key string, destination string, sortOptions *options.SortOptions) (int64, error) + SortReadOnly(key string) ([]Result[string], error) - // Sorts the elements in the list, set, or sorted set at key and returns the result. - // The sort command can be used to sort elements based on different criteria and apply - // transformations on sorted elements. - // This command is routed depending on the client's ReadFrom strategy. - // - // Note: - // In cluster mode, if `key` map to different hash slots, the command - // will be split across these slots and executed separately for each. This means the command - // is atomic only at the slot level. If one or more slot-specific requests fail, the entire - // call will return the first encountered error, even though some requests may have succeeded - // while others did not. If this behavior impacts your application logic, consider splitting - // the request into sub-requests per slot to ensure atomicity. - // The use of SortOptions.byPattern and SortOptions.getPatterns in cluster mode is - // supported since Valkey version 8.0. - // - // Parameters: - // key - The key of the list, set, or sorted set to be sorted. - // sortOptions - The SortOptions type. - // - // Return value: - // An Array of sorted elements. - // - // Example: - // - // options := api.NewSortOptions().SetByPattern("weight_*").SetIsAlpha(false).AddGetPattern("object_*").AddGetPattern("#") - // result, err := client.SortReadOnly("key", options) - // result.Value(): [{Object_3 false} {c false} {Object_1 false} {a false} {Object_2 false} {b false}] - // result.IsNil(): false - // - // [valkey.io]: https://valkey.io/commands/sort/ SortReadOnlyWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) } diff --git a/go/api/hash_commands.go b/go/api/hash_commands.go index e5100a1d23..b3a4caecec 100644 --- a/go/api/hash_commands.go +++ b/go/api/hash_commands.go @@ -24,11 +24,11 @@ type HashCommands interface { HLen(key string) (int64, error) - HVals(key string) ([]Result[string], error) + HVals(key string) ([]string, error) HExists(key string, field string) (bool, error) - HKeys(key string) ([]Result[string], error) + HKeys(key string) ([]string, error) HStrLen(key string, field string) (int64, error) diff --git a/go/api/list_commands.go b/go/api/list_commands.go index 40ead1674b..88dc0c5247 100644 --- a/go/api/list_commands.go +++ b/go/api/list_commands.go @@ -8,24 +8,23 @@ package api // // [valkey.io]: https://valkey.io/commands/#list type ListCommands interface { - LPush(key string, elements []string) (int64, error) LPop(key string) (Result[string], error) - LPopCount(key string, count int64) ([]Result[string], error) + LPopCount(key string, count int64) ([]string, error) LPos(key string, element string) (Result[int64], error) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) - LPosCount(key string, element string, count int64) ([]Result[int64], error) + LPosCount(key string, element string, count int64) ([]int64, error) - LPosCountWithOptions(key string, element string, count int64, options *LPosOptions) ([]Result[int64], error) + LPosCountWithOptions(key string, element string, count int64, options *LPosOptions) ([]int64, error) RPush(key string, elements []string) (int64, error) - LRange(key string, start int64, end int64) ([]Result[string], error) + LRange(key string, start int64, end int64) ([]string, error) LIndex(key string, index int64) (Result[string], error) @@ -37,13 +36,13 @@ type ListCommands interface { RPop(key string) (Result[string], error) - RPopCount(key string, count int64) ([]Result[string], error) + RPopCount(key string, count int64) ([]string, error) LInsert(key string, insertPosition InsertPosition, pivot string, element string) (int64, error) - BLPop(keys []string, timeoutSecs float64) ([]Result[string], error) + BLPop(keys []string, timeoutSecs float64) ([]string, error) - BRPop(keys []string, timeoutSecs float64) ([]Result[string], error) + BRPop(keys []string, timeoutSecs float64) ([]string, error) RPushX(key string, elements []string) (int64, error) diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 48a7dc7509..98ba2713d2 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -168,7 +168,8 @@ func handleStringOrNilResponse(response *C.struct_CommandResponse) (Result[strin return convertCharArrayToString(response, true) } -func convertStringArray(response *C.struct_CommandResponse) ([]Result[string], error) { +// Fix after merging with https://github.com/valkey-io/valkey-glide/pull/2964 +func convertStringOrNilArray(response *C.struct_CommandResponse) ([]Result[string], error) { typeErr := checkResponseType(response, C.Array, false) if typeErr != nil { return nil, typeErr @@ -185,35 +186,46 @@ func convertStringArray(response *C.struct_CommandResponse) ([]Result[string], e return slice, nil } -func handleStringArrayResponse(response *C.struct_CommandResponse) ([]Result[string], error) { - defer C.free_command_response(response) - - return convertStringArray(response) -} - -func handleStringArrayOrNullResponse(response *C.struct_CommandResponse) ([]Result[string], error) { - defer C.free_command_response(response) - - typeErr := checkResponseType(response, C.Array, true) +// array could be nillable, but strings - aren't +func convertStringArray(response *C.struct_CommandResponse, isNilable bool) ([]string, error) { + typeErr := checkResponseType(response, C.Array, isNilable) if typeErr != nil { return nil, typeErr } - if response.response_type == C.Null { + if isNilable && response.array_value == nil { return nil, nil } - slice := make([]Result[string], 0, response.array_value_len) + slice := make([]string, 0, response.array_value_len) for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { - res, err := convertCharArrayToString(&v, true) + res, err := convertCharArrayToString(&v, false) if err != nil { return nil, err } - slice = append(slice, res) + slice = append(slice, res.Value()) } return slice, nil } +func handleStringOrNilArrayResponse(response *C.struct_CommandResponse) ([]Result[string], error) { + defer C.free_command_response(response) + + return convertStringOrNilArray(response) +} + +func handleStringArrayResponse(response *C.struct_CommandResponse) ([]string, error) { + defer C.free_command_response(response) + + return convertStringArray(response, false) +} + +func handleStringArrayOrNilResponse(response *C.struct_CommandResponse) ([]string, error) { + defer C.free_command_response(response) + + return convertStringArray(response, true) +} + func handleIntResponse(response *C.struct_CommandResponse) (int64, error) { defer C.free_command_response(response) @@ -240,7 +252,7 @@ func handleIntOrNilResponse(response *C.struct_CommandResponse) (Result[int64], return CreateInt64Result(int64(response.int_value)), nil } -func handleIntArrayResponse(response *C.struct_CommandResponse) ([]Result[int64], error) { +func handleIntArrayResponse(response *C.struct_CommandResponse) ([]int64, error) { defer C.free_command_response(response) typeErr := checkResponseType(response, C.Array, false) @@ -248,13 +260,13 @@ func handleIntArrayResponse(response *C.struct_CommandResponse) ([]Result[int64] return nil, typeErr } - slice := make([]Result[int64], 0, response.array_value_len) + slice := make([]int64, 0, response.array_value_len) for _, v := range unsafe.Slice(response.array_value, response.array_value_len) { err := checkResponseType(&v, C.Int, false) if err != nil { return nil, err } - slice = append(slice, CreateInt64Result(int64(v.int_value))) + slice = append(slice, int64(v.int_value)) } return slice, nil } @@ -407,7 +419,7 @@ func handleStringToStringArrayMapOrNullResponse( if err != nil { return nil, err } - value, err := convertStringArray(v.map_value) + value, err := convertStringOrNilArray(v.map_value) if err != nil { return nil, err } diff --git a/go/api/sorted_set_commands.go b/go/api/sorted_set_commands.go index f6cd9f9256..babc7106c6 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -37,7 +37,7 @@ type SortedSetCommands interface { BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error) - ZRange(key string, rangeQuery options.ZRangeQuery) ([]Result[string], error) + ZRange(key string, rangeQuery options.ZRangeQuery) ([]string, error) ZRangeWithScores(key string, rangeQuery options.ZRangeQueryWithScores) (map[Result[string]]Result[float64], error) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 28421c428e..4f83224e2f 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -922,11 +922,8 @@ func (suite *GlideTestSuite) TestHVals_WithExistingKey() { assert.Equal(suite.T(), int64(2), res1) res2, err := client.HVals(key) - value1 := api.CreateStringResult("value1") - value2 := api.CreateStringResult("value2") assert.Nil(suite.T(), err) - assert.Contains(suite.T(), res2, value1) - assert.Contains(suite.T(), res2, value2) + assert.ElementsMatch(suite.T(), []string{"value1", "value2"}, res2) }) } @@ -936,7 +933,7 @@ func (suite *GlideTestSuite) TestHVals_WithNotExistingKey() { res, err := client.HVals(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res) + assert.Empty(suite.T(), res) }) } @@ -990,11 +987,8 @@ func (suite *GlideTestSuite) TestHKeys_WithExistingKey() { assert.Equal(suite.T(), int64(2), res1) res2, err := client.HKeys(key) - field1 := api.CreateStringResult("field1") - field2 := api.CreateStringResult("field2") assert.Nil(suite.T(), err) - assert.Contains(suite.T(), res2, field1) - assert.Contains(suite.T(), res2, field2) + assert.ElementsMatch(suite.T(), []string{"field1", "field2"}, res2) }) } @@ -1004,7 +998,7 @@ func (suite *GlideTestSuite) TestHKeys_WithNotExistingKey() { res, err := client.HKeys(key) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res) + assert.Empty(suite.T(), res) }) } @@ -1295,10 +1289,9 @@ func (suite *GlideTestSuite) TestLPushLPop_WithExistingKey() { assert.Nil(suite.T(), err) assert.Equal(suite.T(), "value1", res2.Value()) - resultList := []api.Result[string]{api.CreateStringResult("value2"), api.CreateStringResult("value3")} res3, err := client.LPopCount(key, 2) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, res3) + assert.Equal(suite.T(), []string{"value2", "value3"}, res3) }) } @@ -1312,7 +1305,7 @@ func (suite *GlideTestSuite) TestLPop_nonExistingKey() { res2, err := client.LPopCount(key, 2) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res2) + assert.Nil(suite.T(), res2) }) } @@ -1327,7 +1320,7 @@ func (suite *GlideTestSuite) TestLPushLPop_typeError() { assert.IsType(suite.T(), &api.RequestError{}, err) res2, err := client.LPopCount(key, 2) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res2) + assert.Nil(suite.T(), res2) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -1412,33 +1405,29 @@ func (suite *GlideTestSuite) TestLPosCount() { assert.Nil(suite.T(), err) res2, err := client.LPosCount(key, "a", int64(2)) - assert.Equal(suite.T(), []api.Result[int64]{api.CreateInt64Result(0), api.CreateInt64Result(1)}, res2) + assert.Equal(suite.T(), []int64{0, 1}, res2) assert.Nil(suite.T(), err) res3, err := client.LPosCount(key, "a", int64(0)) - assert.Equal( - suite.T(), - []api.Result[int64]{api.CreateInt64Result(0), api.CreateInt64Result(1), api.CreateInt64Result(4)}, - res3, - ) + assert.Equal(suite.T(), []int64{0, 1, 4}, res3) assert.Nil(suite.T(), err) // invalid count value res4, err := client.LPosCount(key, "a", int64(-1)) - assert.Equal(suite.T(), ([]api.Result[int64])(nil), res4) + assert.Nil(suite.T(), res4) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) // non-existent key res5, err := client.LPosCount("non_existent_key", "a", int64(1)) - assert.Equal(suite.T(), []api.Result[int64]{}, res5) + assert.Empty(suite.T(), res5) assert.Nil(suite.T(), err) // wrong key data type keyString := uuid.NewString() suite.verifyOK(client.Set(keyString, "value")) res6, err := client.LPosCount(keyString, "a", int64(1)) - assert.Equal(suite.T(), ([]api.Result[int64])(nil), res6) + assert.Nil(suite.T(), res6) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -1453,24 +1442,16 @@ func (suite *GlideTestSuite) TestLPosCount_withOptions() { assert.Nil(suite.T(), err) res2, err := client.LPosCountWithOptions(key, "a", int64(0), api.NewLPosOptionsBuilder().SetRank(1)) - assert.Equal( - suite.T(), - []api.Result[int64]{api.CreateInt64Result(0), api.CreateInt64Result(1), api.CreateInt64Result(4)}, - res2, - ) + assert.Equal(suite.T(), []int64{0, 1, 4}, res2) assert.Nil(suite.T(), err) res3, err := client.LPosCountWithOptions(key, "a", int64(0), api.NewLPosOptionsBuilder().SetRank(2)) - assert.Equal(suite.T(), []api.Result[int64]{api.CreateInt64Result(1), api.CreateInt64Result(4)}, res3) + assert.Equal(suite.T(), []int64{1, 4}, res3) assert.Nil(suite.T(), err) // reverse traversal res4, err := client.LPosCountWithOptions(key, "a", int64(0), api.NewLPosOptionsBuilder().SetRank(-1)) - assert.Equal( - suite.T(), - []api.Result[int64]{api.CreateInt64Result(4), api.CreateInt64Result(1), api.CreateInt64Result(0)}, - res4, - ) + assert.Equal(suite.T(), []int64{4, 1, 0}, res4) assert.Nil(suite.T(), err) }) } @@ -2423,25 +2404,19 @@ func (suite *GlideTestSuite) TestLRange() { assert.Nil(suite.T(), err) assert.Equal(suite.T(), int64(4), res1) - resultList := []api.Result[string]{ - api.CreateStringResult("value1"), - api.CreateStringResult("value2"), - api.CreateStringResult("value3"), - api.CreateStringResult("value4"), - } res2, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, res2) + assert.Equal(suite.T(), []string{"value1", "value2", "value3", "value4"}, res2) res3, err := client.LRange("non_existing_key", int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res3) + assert.Empty(suite.T(), res3) key2 := uuid.NewString() suite.verifyOK(client.Set(key2, "value")) res4, err := client.LRange(key2, int64(0), int64(1)) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res4) + assert.Nil(suite.T(), res4) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -2493,13 +2468,13 @@ func (suite *GlideTestSuite) TestLTrim() { res2, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")}, res2) + assert.Equal(suite.T(), []string{"value1", "value2"}, res2) suite.verifyOK(client.LTrim(key, int64(4), int64(2))) res3, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res3) + assert.Empty(suite.T(), res3) key2 := uuid.NewString() suite.verifyOK(client.Set(key2, "value")) @@ -2552,29 +2527,21 @@ func (suite *GlideTestSuite) TestLRem() { assert.Equal(suite.T(), int64(2), res2) res3, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("value2"), - api.CreateStringResult("value2"), - api.CreateStringResult("value1"), - }, - res3, - ) + assert.Equal(suite.T(), []string{"value2", "value2", "value1"}, res3) res4, err := client.LRem(key, -1, "value2") assert.Nil(suite.T(), err) assert.Equal(suite.T(), int64(1), res4) res5, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult("value2"), api.CreateStringResult("value1")}, res5) + assert.Equal(suite.T(), []string{"value2", "value1"}, res5) res6, err := client.LRem(key, 0, "value2") assert.Nil(suite.T(), err) assert.Equal(suite.T(), int64(1), res6) res7, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult("value1")}, res7) + assert.Equal(suite.T(), []string{"value1"}, res7) res8, err := client.LRem("non_existing_key", 0, "value") assert.Nil(suite.T(), err) @@ -2598,14 +2565,14 @@ func (suite *GlideTestSuite) TestRPopAndRPopCount() { res3, err := client.RPopCount(key, int64(2)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult("value3"), api.CreateStringResult("value2")}, res3) + assert.Equal(suite.T(), []string{"value3", "value2"}, res3) res4, err := client.RPop("non_existing_key") assert.Nil(suite.T(), err) assert.Equal(suite.T(), api.CreateNilStringResult(), res4) res5, err := client.RPopCount("non_existing_key", int64(2)) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res5) + assert.Nil(suite.T(), res5) assert.Nil(suite.T(), err) key2 := uuid.NewString() @@ -2617,7 +2584,7 @@ func (suite *GlideTestSuite) TestRPopAndRPopCount() { assert.IsType(suite.T(), &api.RequestError{}, err) res7, err := client.RPopCount(key2, int64(2)) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res7) + assert.Nil(suite.T(), res7) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -2642,18 +2609,7 @@ func (suite *GlideTestSuite) TestLInsert() { res4, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("value1"), - api.CreateStringResult("value1.5"), - api.CreateStringResult("value2"), - api.CreateStringResult("value3"), - api.CreateStringResult("value3.5"), - api.CreateStringResult("value4"), - }, - res4, - ) + assert.Equal(suite.T(), []string{"value1", "value1.5", "value2", "value3", "value3.5", "value4"}, res4) res5, err := client.LInsert("non_existing_key", api.Before, "pivot", "elem") assert.Nil(suite.T(), err) @@ -2684,17 +2640,17 @@ func (suite *GlideTestSuite) TestBLPop() { res2, err := client.BLPop([]string{listKey1, listKey2}, float64(0.5)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult(listKey1), api.CreateStringResult("value2")}, res2) + assert.Equal(suite.T(), []string{listKey1, "value2"}, res2) res3, err := client.BLPop([]string{listKey2}, float64(1.0)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res3) + assert.Nil(suite.T(), res3) key := uuid.NewString() suite.verifyOK(client.Set(key, "value")) res4, err := client.BLPop([]string{key}, float64(1.0)) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res4) + assert.Nil(suite.T(), res4) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -2711,17 +2667,17 @@ func (suite *GlideTestSuite) TestBRPop() { res2, err := client.BRPop([]string{listKey1, listKey2}, float64(0.5)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{api.CreateStringResult(listKey1), api.CreateStringResult("value1")}, res2) + assert.Equal(suite.T(), []string{listKey1, "value1"}, res2) res3, err := client.BRPop([]string{listKey2}, float64(1.0)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res3) + assert.Nil(suite.T(), res3) key := uuid.NewString() suite.verifyOK(client.Set(key, "value")) res4, err := client.BRPop([]string{key}, float64(1.0)) - assert.Equal(suite.T(), ([]api.Result[string])(nil), res4) + assert.Nil(suite.T(), res4) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -2743,16 +2699,7 @@ func (suite *GlideTestSuite) TestRPushX() { res3, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("value1"), - api.CreateStringResult("value2"), - api.CreateStringResult("value3"), - api.CreateStringResult("value4"), - }, - res3, - ) + assert.Equal(suite.T(), []string{"value1", "value2", "value3", "value4"}, res3) res4, err := client.RPushX(key2, []string{"value1"}) assert.Nil(suite.T(), err) @@ -2760,7 +2707,7 @@ func (suite *GlideTestSuite) TestRPushX() { res5, err := client.LRange(key2, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res5) + assert.Empty(suite.T(), res5) suite.verifyOK(client.Set(key3, "value")) @@ -2792,16 +2739,7 @@ func (suite *GlideTestSuite) TestLPushX() { res3, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("value4"), - api.CreateStringResult("value3"), - api.CreateStringResult("value2"), - api.CreateStringResult("value1"), - }, - res3, - ) + assert.Equal(suite.T(), []string{"value4", "value3", "value2", "value1"}, res3) res4, err := client.LPushX(key2, []string{"value1"}) assert.Nil(suite.T(), err) @@ -2809,7 +2747,7 @@ func (suite *GlideTestSuite) TestLPushX() { res5, err := client.LRange(key2, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal(suite.T(), []api.Result[string]{}, res5) + assert.Empty(suite.T(), res5) suite.verifyOK(client.Set(key3, "value")) @@ -2953,31 +2891,13 @@ func (suite *GlideTestSuite) TestLSet() { res5, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("zero"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - api.CreateStringResult("four"), - }, - res5, - ) + assert.Equal(suite.T(), []string{"zero", "two", "three", "four"}, res5) suite.verifyOK(client.LSet(key, int64(-1), "zero")) res7, err := client.LRange(key, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("zero"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - api.CreateStringResult("zero"), - }, - res7, - ) + assert.Equal(suite.T(), []string{"zero", "two", "three", "zero"}, res7) }) } @@ -3006,15 +2926,7 @@ func (suite *GlideTestSuite) TestLMove() { res4, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - }, - res4, - ) + assert.Equal(suite.T(), []string{"one", "two", "three"}, res4) // source and destination are the same, performing list rotation, "one" gets popped and added back res5, err := client.LMove(key1, key1, api.Left, api.Left) @@ -3023,15 +2935,7 @@ func (suite *GlideTestSuite) TestLMove() { res6, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - }, - res6, - ) + assert.Equal(suite.T(), []string{"one", "two", "three"}, res6) // normal use case, "three" gets popped and added to the left of destination res7, err := client.LPush(key2, []string{"six", "five", "four"}) assert.Nil(suite.T(), err) @@ -3043,26 +2947,10 @@ func (suite *GlideTestSuite) TestLMove() { res9, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - }, - res9, - ) + assert.Equal(suite.T(), []string{"one", "two"}, res9) res10, err := client.LRange(key2, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("three"), - api.CreateStringResult("four"), - api.CreateStringResult("five"), - api.CreateStringResult("six"), - }, - res10, - ) + assert.Equal(suite.T(), []string{"three", "four", "five", "six"}, res10) // source exists but is not a list type key suite.verifyOK(client.Set(nonListKey, "value")) @@ -3888,18 +3776,11 @@ func (suite *GlideTestSuite) TestSortStore_BasicSorting() { assert.Nil(suite.T(), err) assert.NotNil(suite.T(), result) - assert.Equal(suite.T(), int64(5), result.Value()) + assert.Equal(suite.T(), int64(5), result) sortedValues, err := client.LRange(sortedKey, 0, -1) - resultList := []api.Result[string]{ - api.CreateStringResult("1"), - api.CreateStringResult("2"), - api.CreateStringResult("4"), - api.CreateStringResult("5"), - api.CreateStringResult("10"), - } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, sortedValues) + assert.Equal(suite.T(), []string{"1", "2", "4", "5", "10"}, sortedValues) }) } @@ -3908,7 +3789,7 @@ func (suite *GlideTestSuite) TestSortStore_ErrorHandling() { result, err := client.SortStore("{listKey}nonExistingKey", "{listKey}mydestinationKey") assert.Nil(suite.T(), err) - assert.Equal(suite.T(), int64(0), result.Value()) + assert.Equal(suite.T(), int64(0), result) }) } @@ -3923,18 +3804,11 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_DescendingOrder() { assert.Nil(suite.T(), err) assert.NotNil(suite.T(), result) - assert.Equal(suite.T(), int64(5), result.Value()) + assert.Equal(suite.T(), int64(5), result) sortedValues, err := client.LRange(sortedKey, 0, -1) - resultList := []api.Result[string]{ - api.CreateStringResult("50"), - api.CreateStringResult("40"), - api.CreateStringResult("30"), - api.CreateStringResult("20"), - api.CreateStringResult("10"), - } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, sortedValues) + assert.Equal(suite.T(), []string{"50", "40", "30", "20", "10"}, sortedValues) }) } @@ -3949,16 +3823,10 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_AlphaSorting() { assert.Nil(suite.T(), err) assert.NotNil(suite.T(), result) - assert.Equal(suite.T(), int64(5), result.Value()) + assert.Equal(suite.T(), int64(5), result) sortedValues, err := client.LRange(sortedKey, 0, -1) - resultList := []api.Result[string]{ - api.CreateStringResult("apple"), - api.CreateStringResult("banana"), - api.CreateStringResult("cherry"), - api.CreateStringResult("date"), - api.CreateStringResult("elderberry"), - } + resultList := []string{"apple", "banana", "cherry", "date", "elderberry"} assert.Nil(suite.T(), err) assert.Equal(suite.T(), resultList, sortedValues) }) @@ -3975,16 +3843,11 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_Limit() { assert.Nil(suite.T(), err) assert.NotNil(suite.T(), result) - assert.Equal(suite.T(), int64(3), result.Value()) + assert.Equal(suite.T(), int64(3), result) sortedValues, err := client.LRange(sortedKey, 0, -1) - resultList := []api.Result[string]{ - api.CreateStringResult("20"), - api.CreateStringResult("30"), - api.CreateStringResult("40"), - } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, sortedValues) + assert.Equal(suite.T(), []string{"20", "30", "40"}, sortedValues) }) } @@ -4057,15 +3920,7 @@ func (suite *GlideTestSuite) TestBLMove() { res4, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - }, - res4, - ) + assert.Equal(suite.T(), []string{"one", "two", "three"}, res4) // source and destination are the same, performing list rotation, "one" gets popped and added back res5, err := client.BLMove(key1, key1, api.Left, api.Left, float64(0.1)) @@ -4074,15 +3929,7 @@ func (suite *GlideTestSuite) TestBLMove() { res6, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - api.CreateStringResult("three"), - }, - res6, - ) + assert.Equal(suite.T(), []string{"one", "two", "three"}, res6) // normal use case, "three" gets popped and added to the left of destination res7, err := client.LPush(key2, []string{"six", "five", "four"}) assert.Nil(suite.T(), err) @@ -4094,26 +3941,11 @@ func (suite *GlideTestSuite) TestBLMove() { res9, err := client.LRange(key1, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("one"), - api.CreateStringResult("two"), - }, - res9, - ) + assert.Equal(suite.T(), []string{"one", "two"}, res9) + res10, err := client.LRange(key2, int64(0), int64(-1)) assert.Nil(suite.T(), err) - assert.Equal( - suite.T(), - []api.Result[string]{ - api.CreateStringResult("three"), - api.CreateStringResult("four"), - api.CreateStringResult("five"), - api.CreateStringResult("six"), - }, - res10, - ) + assert.Equal(suite.T(), []string{"three", "four", "five", "six"}, res10) // source exists but is not a list type key suite.verifyOK(client.Set(nonListKey, "value")) @@ -4170,7 +4002,7 @@ func (suite *GlideTestSuite) TestType() { suite.verifyOK(client.Set(keyName, initialValue)) result, err := client.Type(keyName) assert.Nil(suite.T(), err) - assert.IsType(suite.T(), result, api.CreateStringResult("string"), "Value is string") + assert.IsType(suite.T(), result, "string", "Value is string") // Test 2: Check if the value is list key1 := "{keylist}-1" + uuid.NewString() @@ -4179,7 +4011,7 @@ func (suite *GlideTestSuite) TestType() { assert.Nil(suite.T(), err) resultType, err := client.Type(key1) assert.Nil(suite.T(), err) - assert.IsType(suite.T(), resultType, api.CreateStringResult("list"), "Value is list") + assert.IsType(suite.T(), resultType, "list", "Value is list") }) } @@ -4231,7 +4063,7 @@ func (suite *GlideTestSuite) TestRename() { // Test 2 Check if the rename command return false if the key/newkey is invalid. key1 := "{keyName}" + uuid.NewString() res1, err := client.Rename(key1, "invalidKey") - assert.Equal(suite.T(), "", res1.Value()) + assert.Equal(suite.T(), "", res1) assert.NotNil(suite.T(), err) assert.IsType(suite.T(), &api.RequestError{}, err) }) @@ -4932,21 +4764,12 @@ func (suite *GlideTestSuite) TestZRange() { assert.NoError(t, err) // index [0:1] res, err := client.ZRange(key, options.NewRangeByIndexQuery(0, 1)) - expected := []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"a", "b"}, res) // index [0:-1] (all) res, err = client.ZRange(key, options.NewRangeByIndexQuery(0, -1)) - expected = []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - api.CreateStringResult("c"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"a", "b", "c"}, res) // index [3:1] (none) res, err = client.ZRange(key, options.NewRangeByIndexQuery(3, 1)) assert.NoError(t, err) @@ -4957,48 +4780,31 @@ func (suite *GlideTestSuite) TestZRange() { options.NewInfiniteScoreBoundary(options.NegativeInfinity), options.NewScoreBoundary(3, true)) res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - api.CreateStringResult("c"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"a", "b", "c"}, res) // score [-inf:3) query = options.NewRangeByScoreQuery( options.NewInfiniteScoreBoundary(options.NegativeInfinity), options.NewScoreBoundary(3, false)) res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"a", "b"}, res) // score (3:-inf] reverse query = options.NewRangeByScoreQuery( options.NewScoreBoundary(3, false), options.NewInfiniteScoreBoundary(options.NegativeInfinity)). SetReverse() res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("b"), - api.CreateStringResult("a"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"b", "a"}, res) // score [-inf:+inf] limit 1 2 query = options.NewRangeByScoreQuery( options.NewInfiniteScoreBoundary(options.NegativeInfinity), options.NewInfiniteScoreBoundary(options.PositiveInfinity)). SetLimit(1, 2) res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("b"), - api.CreateStringResult("c"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"b", "c"}, res) // score [-inf:3) reverse (none) query = options.NewRangeByScoreQuery( options.NewInfiniteScoreBoundary(options.NegativeInfinity), @@ -5019,36 +4825,24 @@ func (suite *GlideTestSuite) TestZRange() { options.NewInfiniteLexBoundary(options.NegativeInfinity), options.NewLexBoundary("c", false)) res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("a"), - api.CreateStringResult("b"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"a", "b"}, res) // lex [+:-] reverse limit 1 2 query = options.NewRangeByLexQuery( options.NewInfiniteLexBoundary(options.PositiveInfinity), options.NewInfiniteLexBoundary(options.NegativeInfinity)). SetReverse().SetLimit(1, 2) res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("b"), - api.CreateStringResult("a"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"b", "a"}, res) // lex (c:-] reverse query = options.NewRangeByLexQuery( options.NewLexBoundary("c", false), options.NewInfiniteLexBoundary(options.NegativeInfinity)). SetReverse() res, err = client.ZRange(key, query) - expected = []api.Result[string]{ - api.CreateStringResult("b"), - api.CreateStringResult("a"), - } assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, []string{"b", "a"}, res) // lex [+:c] (none) query = options.NewRangeByLexQuery( options.NewInfiniteLexBoundary(options.PositiveInfinity), @@ -6694,18 +6488,11 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_ByPattern() { assert.Nil(suite.T(), err) assert.NotNil(suite.T(), result) - assert.Equal(suite.T(), int64(5), result.Value()) + assert.Equal(suite.T(), int64(5), result) sortedValues, err := client.LRange(sortedKey, 0, -1) - resultList := []api.Result[string]{ - api.CreateStringResult("d"), - api.CreateStringResult("b"), - api.CreateStringResult("c"), - api.CreateStringResult("e"), - api.CreateStringResult("a"), - } assert.Nil(suite.T(), err) - assert.Equal(suite.T(), resultList, sortedValues) + assert.Equal(suite.T(), []string{"d", "b", "c", "e", "a"}, sortedValues) }) } From 6377cfb3649298b0fe3f87c1ff5636fc8747d913 Mon Sep 17 00:00:00 2001 From: Yury-Fridlyand Date: Mon, 20 Jan 2025 12:01:29 -0800 Subject: [PATCH 15/21] Go: Fix return types, parn 5 (#2964) Fix return types Signed-off-by: Yury-Fridlyand Signed-off-by: Edward Liang --- go/api/base_client.go | 92 ++++----- go/api/glide_client.go | 2 +- go/api/hash_commands.go | 2 +- go/api/list_commands.go | 8 +- go/api/response_handlers.go | 99 +++++---- go/api/server_management_commands.go | 6 +- go/api/set_commands.go | 8 +- go/api/sorted_set_commands.go | 11 +- go/integTest/shared_commands_test.go | 253 +++++++++-------------- go/integTest/standalone_commands_test.go | 8 +- 10 files changed, 206 insertions(+), 283 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index d21aab2d84..2c1f914cc0 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -869,14 +869,10 @@ func (client *Command) HGet(key string, field string) (Result[string], error) { // 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/ -func (client *Command) 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 @@ -1670,21 +1666,17 @@ func (client *Command) SUnionStore(destination string, keys []string) (int64, er // // 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/ -func (client *Command) 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 @@ -1765,20 +1757,17 @@ func (client *Command) SIsMember(key string, member string) (bool, error) { // // 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/ -func (client *Command) 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 @@ -1831,20 +1820,17 @@ func (client *Command) SDiffStore(destination string, keys []string) (int64, err // // 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/ -func (client *Command) 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 @@ -2069,8 +2055,8 @@ func (client *Command) SMIsMember(key string, members []string) ([]bool, error) // // 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: // @@ -2083,15 +2069,15 @@ func (client *Command) SMIsMember(key string, members []string) ([]bool, error) // // 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/ -func (client *Command) 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 @@ -2670,10 +2656,10 @@ func (client *Command) LPushX(key string, elements []string) (int64, error) { // // 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/ -func (client *Command) 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 @@ -2694,7 +2680,7 @@ func (client *Command) LMPop(keys []string, listDirection ListDirection) (map[Re return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } // Pops one or more elements from the first non-empty list from the provided keys. @@ -2719,14 +2705,14 @@ func (client *Command) LMPop(keys []string, listDirection ListDirection) (map[Re // // 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/ func (client *Command) 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 @@ -2747,7 +2733,7 @@ func (client *Command) LMPopCount( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } // Blocks the connection until it pops one element from the first non-empty list from the provided keys. BLMPop is the @@ -2779,7 +2765,7 @@ func (client *Command) LMPopCount( // // 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 @@ -2787,7 +2773,7 @@ func (client *Command) 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 @@ -2808,7 +2794,7 @@ func (client *Command) BLMPop( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } // Blocks the connection until it pops one or more elements from the first non-empty list from the provided keys. @@ -2842,7 +2828,7 @@ func (client *Command) BLMPop( // // 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 @@ -2851,7 +2837,7 @@ func (client *Command) 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 @@ -2872,7 +2858,7 @@ func (client *Command) BLMPopCount( return nil, err } - return handleStringToStringArrayMapOrNullResponse(result) + return handleStringToStringArrayMapOrNilResponse(result) } // Sets the list element at index to element. @@ -4358,10 +4344,10 @@ func (client *Command) ZIncrBy(key string, increment float64, member string) (fl // 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/ -func (client *Command) 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 @@ -4388,10 +4374,10 @@ func (client *Command) ZPopMin(key string) (map[Result[string]]Result[float64], // 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/ -func (client *Command) 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 @@ -4417,10 +4403,10 @@ func (client *Command) ZPopMinWithCount(key string, count int64) (map[Result[str // 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/ -func (client *Command) 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 @@ -4447,10 +4433,10 @@ func (client *Command) ZPopMax(key string) (map[Result[string]]Result[float64], // 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/ -func (client *Command) 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 @@ -4638,7 +4624,7 @@ func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([] func (client *Command) 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()...) 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 b3a4caecec..f524da3157 100644 --- a/go/api/hash_commands.go +++ b/go/api/hash_commands.go @@ -12,7 +12,7 @@ import "github.com/valkey-io/valkey-glide/go/glide/api/options" type HashCommands interface { HGet(key string, field string) (Result[string], error) - HGetAll(key string) (map[Result[string]]Result[string], error) + HGetAll(key string) (map[string]string, error) HMGet(key string, fields []string) ([]Result[string], error) diff --git a/go/api/list_commands.go b/go/api/list_commands.go index 88dc0c5247..d1f688bb5c 100644 --- a/go/api/list_commands.go +++ b/go/api/list_commands.go @@ -48,18 +48,18 @@ type ListCommands interface { LPushX(key string, elements []string) (int64, error) - LMPop(keys []string, listDirection ListDirection) (map[Result[string]][]Result[string], error) + LMPop(keys []string, listDirection ListDirection) (map[string][]string, error) - LMPopCount(keys []string, listDirection ListDirection, count int64) (map[Result[string]][]Result[string], error) + LMPopCount(keys []string, listDirection ListDirection, count int64) (map[string][]string, error) - BLMPop(keys []string, listDirection ListDirection, timeoutSecs float64) (map[Result[string]][]Result[string], error) + BLMPop(keys []string, listDirection ListDirection, timeoutSecs float64) (map[string][]string, error) BLMPopCount( keys []string, listDirection ListDirection, count int64, timeoutSecs float64, - ) (map[Result[string]][]Result[string], error) + ) (map[string][]string, error) LSet(key string, index int64, element string) (string, error) diff --git a/go/api/response_handlers.go b/go/api/response_handlers.go index 98ba2713d2..dd7fbe2712 100644 --- a/go/api/response_handlers.go +++ b/go/api/response_handlers.go @@ -351,7 +351,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 +359,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 + } + result, ok := converted.(map[string]float64) + if !ok { + return nil, &RequestError{fmt.Sprintf("unexpected type of map: %T", converted)} } - return m, nil + 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 +386,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 +419,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 + } + + converters := mapConverter[[]string]{ + arrayConverter[string]{}, + false, } - return m, nil + 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 +448,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 6dcc2290eb..cca25602bc 100644 --- a/go/api/set_commands.go +++ b/go/api/set_commands.go @@ -14,17 +14,17 @@ type SetCommands interface { SRem(key string, members []string) (int64, error) - SMembers(key string) (map[Result[string]]struct{}, error) + SMembers(key string) (map[string]struct{}, error) SCard(key string) (int64, error) SIsMember(key string, member string) (bool, error) - SDiff(keys []string) (map[Result[string]]struct{}, error) + SDiff(keys []string) (map[string]struct{}, error) SDiffStore(destination string, keys []string) (int64, error) - SInter(keys []string) (map[Result[string]]struct{}, error) + SInter(keys []string) (map[string]struct{}, error) SInterStore(destination string, keys []string) (int64, error) @@ -40,7 +40,7 @@ type SetCommands interface { SUnionStore(destination string, keys []string) (int64, error) - 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 babc7106c6..1aecdfeb1d 100644 --- a/go/api/sorted_set_commands.go +++ b/go/api/sorted_set_commands.go @@ -12,7 +12,6 @@ import ( // // [valkey.io]: https://valkey.io/commands/#sorted-set type SortedSetCommands interface { - ZAdd(key string, membersScoreMap map[string]float64) (int64, error) ZAddWithOptions(key string, membersScoreMap map[string]float64, opts *options.ZAddOptions) (int64, error) @@ -23,13 +22,13 @@ type SortedSetCommands interface { ZIncrBy(key string, increment float64, member string) (float64, error) - ZPopMin(key string) (map[Result[string]]Result[float64], error) + ZPopMin(key string) (map[string]float64, error) - ZPopMinWithCount(key string, count int64) (map[Result[string]]Result[float64], error) + ZPopMinWithCount(key string, count int64) (map[string]float64, error) - ZPopMax(key string) (map[Result[string]]Result[float64], error) + ZPopMax(key string) (map[string]float64, error) - ZPopMaxWithCount(key string, count int64) (map[Result[string]]Result[float64], error) + ZPopMaxWithCount(key string, count int64) (map[string]float64, error) ZRem(key string, members []string) (int64, error) @@ -39,7 +38,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) ZRank(key string, member string) (Result[int64], error) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 4f83224e2f..cba42181b0 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -635,9 +635,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 +651,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 +725,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 +810,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) }) } @@ -1568,21 +1558,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 +1761,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 +1787,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 +1811,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 +1846,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 +1887,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 +1896,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 +1905,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 +1940,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 +2088,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 +2115,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 +2160,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 +2173,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 +2186,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 +2195,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 +2208,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 +2221,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 +2717,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 +2734,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 +2742,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 +2751,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 +2773,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 +2790,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 +2798,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 +2807,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) }) @@ -4662,14 +4605,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 +4636,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 +4803,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 +4827,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 +4839,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 +4851,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 +4863,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) @@ -6296,12 +6233,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 +6272,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) 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) } From c01bfa3a015f44f56a440a05fec18a0c6c5750da Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Mon, 20 Jan 2025 12:52:07 -0800 Subject: [PATCH 16/21] added exposed types for GlideClient Signed-off-by: Edward Liang --- go/api/base_client.go | 2 +- go/api/glide_client.go | 24 ++++++++++++------------ go/integTest/glide_test_suite_test.go | 6 +++--- go/integTest/shared_commands_test.go | 12 ++++++------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 2c1f914cc0..d6b666a788 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -22,7 +22,7 @@ import ( "google.golang.org/protobuf/proto" ) -// BaseClient defines an interface for methods common to both [GlideClient] and [GlideClusterClient]. +// BaseClient defines an interface for methods common to both [IGlideClient] and [GlideClusterClient]. type BaseClient interface { StringCommands HashCommands diff --git a/go/api/glide_client.go b/go/api/glide_client.go index 51ea9ef4b4..bb1954145e 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -11,31 +11,31 @@ import ( ) // GlideClient interface compliance check. -var _ GlideClient = (*glideClient)(nil) +var _ IGlideClient = (*GlideClient)(nil) -// GlideClient is a client used for connection in Standalone mode. -type GlideClient interface { +// IGlideClient is a client used for connection in Standalone mode. +type IGlideClient interface { BaseClient GenericCommands ServerManagementCommands } -// glideClient implements standalone mode operations by extending baseClient functionality. -type glideClient struct { +// GlideClient implements standalone mode operations by extending baseClient functionality. +type GlideClient struct { *baseClient } -// NewGlideClient creates a [GlideClient] in standalone mode using the given [GlideClientConfiguration]. -func NewGlideClient(config *GlideClientConfiguration) (GlideClient, error) { +// NewGlideClient creates a [IGlideClient] in standalone mode using the given [GlideClientConfiguration]. +func NewGlideClient(config *GlideClientConfiguration) (IGlideClient, error) { client, err := createClient(config) if err != nil { return nil, err } - return &glideClient{client}, nil + return &GlideClient{client}, nil } -func (client *glideClient) CustomCommand(args []string) (interface{}, error) { +func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { res, err := client.executeCommand(C.CustomCommand, args) if err != nil { return nil, err @@ -43,7 +43,7 @@ func (client *glideClient) CustomCommand(args []string) (interface{}, error) { return handleInterfaceResponse(res) } -func (client *glideClient) ConfigSet(parameters map[string]string) (string, error) { +func (client *GlideClient) ConfigSet(parameters map[string]string) (string, error) { result, err := client.executeCommand(C.ConfigSet, utils.MapToString(parameters)) if err != nil { return "", err @@ -51,7 +51,7 @@ func (client *glideClient) ConfigSet(parameters map[string]string) (string, erro return handleStringResponse(result) } -func (client *glideClient) ConfigGet(args []string) (map[string]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 @@ -59,7 +59,7 @@ func (client *glideClient) ConfigGet(args []string) (map[string]string, error) { return handleStringToStringMapResponse(res) } -func (client *glideClient) Select(index int64) (string, error) { +func (client *GlideClient) Select(index int64) (string, error) { result, err := client.executeCommand(C.Select, []string{utils.IntToString(index)}) if err != nil { return "", err diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go index 9d4fed4fdf..0cd1bd445c 100644 --- a/go/integTest/glide_test_suite_test.go +++ b/go/integTest/glide_test_suite_test.go @@ -24,7 +24,7 @@ type GlideTestSuite struct { clusterHosts []api.NodeAddress tls bool serverVersion string - clients []api.GlideClient + clients []api.IGlideClient clusterClients []api.GlideClusterClient } @@ -227,7 +227,7 @@ func (suite *GlideTestSuite) getDefaultClients() []api.BaseClient { return []api.BaseClient{suite.defaultClient(), suite.defaultClusterClient()} } -func (suite *GlideTestSuite) defaultClient() api.GlideClient { +func (suite *GlideTestSuite) defaultClient() api.IGlideClient { config := api.NewGlideClientConfiguration(). WithAddress(&suite.standaloneHosts[0]). WithUseTLS(suite.tls). @@ -235,7 +235,7 @@ func (suite *GlideTestSuite) defaultClient() api.GlideClient { return suite.client(config) } -func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) api.GlideClient { +func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) api.IGlideClient { client, err := api.NewGlideClient(config) assert.Nil(suite.T(), err) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index cba42181b0..4d7ecdde73 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4098,7 +4098,7 @@ func sendWithCustomCommand(suite *GlideTestSuite, client api.BaseClient, args [] var res any var err error switch c := client.(type) { - case api.GlideClient: + case api.IGlideClient: res, err = c.CustomCommand(args) case api.GlideClusterClient: res, err = c.CustomCommand(args) @@ -4396,7 +4396,7 @@ func (suite *GlideTestSuite) TestXRead() { // ensure that commands doesn't time out even if timeout > request timeout var testClient api.BaseClient - if _, ok := client.(api.GlideClient); ok { + if _, ok := client.(api.IGlideClient); ok { testClient = suite.client(api.NewGlideClientConfiguration(). WithAddress(&suite.standaloneHosts[0]). WithUseTLS(suite.tls)) @@ -5412,7 +5412,7 @@ func (suite *GlideTestSuite) TestXPending() { // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be // collapsed once the native commands are added in a subsequent release. - execStandalone := func(client api.GlideClient) { + execStandalone := func(client api.IGlideClient) { // 1. Arrange the data key := uuid.New().String() groupName := "group" + uuid.New().String() @@ -5568,7 +5568,7 @@ func (suite *GlideTestSuite) TestXPending() { // this is only needed in order to be able to use custom commands. // Once the native commands are added, this logic will be refactored. switch c := client.(type) { - case api.GlideClient: + case api.IGlideClient: execStandalone(c) case api.GlideClusterClient: execCluster(c) @@ -5586,7 +5586,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be // collapsed once the native commands are added in a subsequent release. - execStandalone := func(client api.GlideClient) { + execStandalone := func(client api.IGlideClient) { // 1. Arrange the data key := uuid.New().String() missingKey := uuid.New().String() @@ -5892,7 +5892,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { // this is only needed in order to be able to use custom commands. // Once the native commands are added, this logic will be refactored. switch c := client.(type) { - case api.GlideClient: + case api.IGlideClient: execStandalone(c) case api.GlideClusterClient: execCluster(c) From a5377637ef4d08bfc3a72a2476424ba8a576be34 Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Mon, 20 Jan 2025 13:15:13 -0800 Subject: [PATCH 17/21] fixed indentation issues Signed-off-by: Edward Liang --- go/api/base_client.go | 178 +++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index d6b666a788..1a69fa4416 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -197,8 +197,8 @@ func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) { // // For example: // -// result, err := client.Set("key", "value") -// result: "OK" +// result, err := client.Set("key", "value") +// result: "OK" // // [valkey.io]: https://valkey.io/commands/set/ func (client *Command) Set(key string, value string) (string, error) { @@ -380,8 +380,8 @@ func (client *Command) GetExWithOptions(key string, options *GetExOptions) (Resu // // For example: // -// result, err := client.MSet(map[string]string{"key1": "value1", "key2": "value2"}) -// result: "OK" +// result, err := client.MSet(map[string]string{"key1": "value1", "key2": "value2"}) +// result: "OK" // // [valkey.io]: https://valkey.io/commands/mset/ func (client *Command) MSet(keyValueMap map[string]string) (string, error) { @@ -454,13 +454,13 @@ func (client *Command) MSetNX(keyValueMap map[string]string) (bool, error) { // // For example: // -// key1: value1, key2: value2 -// result, err := client.MGet([]string{"key1", "key2", "key3"}) -// result : { -// api.CreateStringResult("value1), -// api.CreateStringResult("value2"), -// api.CreateNilStringResult() -// } +// key1: value1, key2: value2 +// result, err := client.MGet([]string{"key1", "key2", "key3"}) +// result : { +// api.CreateStringResult("value1), +// api.CreateStringResult("value2"), +// api.CreateNilStringResult() +// } // // [valkey.io]: https://valkey.io/commands/mget/ func (client *Command) MGet(keys []string) ([]Result[string], error) { @@ -1176,9 +1176,9 @@ func (client *Command) HStrLen(key string, field string) (int64, error) { // // Example: // -// _, err := client.HSet("key", map[string]string{"field": "10"}) -// hincrByResult, err := client.HIncrBy("key", "field", 1) -// // hincrByResult: 11 +// _, err := client.HSet("key", map[string]string{"field": "10"}) +// hincrByResult, err := client.HIncrBy("key", "field", 1) +// // hincrByResult: 11 // // [valkey.io]: https://valkey.io/commands/hincrby/ func (client *Command) HIncrBy(key string, field string, increment int64) (int64, error) { @@ -1208,9 +1208,9 @@ func (client *Command) HIncrBy(key string, field string, increment int64) (int64 // // Example: // -// _, err := client.HSet("key", map[string]string{"field": "10"}) -// hincrByFloatResult, err := client.HIncrByFloat("key", "field", 1.5) -// // hincrByFloatResult: 11.5 +// _, err := client.HSet("key", map[string]string{"field": "10"}) +// hincrByFloatResult, err := client.HIncrByFloat("key", "field", 1.5) +// // hincrByFloatResult: 11.5 // // [valkey.io]: https://valkey.io/commands/hincrbyfloat/ func (client *Command) HIncrByFloat(key string, field string, increment float64) (float64, error) { @@ -1263,9 +1263,9 @@ func (client *baseClient) HScan(key string, cursor string) (string, []string, er // // Parameters: // -// key - The key of the hash. -// cursor - The cursor that points to the next iteration of results. A value of "0" indicates the start of the search. -// options - The [api.HashScanOptions]. +// key - The key of the hash. +// cursor - The cursor that points to the next iteration of results. A value of "0" indicates the start of the search. +// options - The [api.HashScanOptions]. // // Return value: // @@ -1277,13 +1277,13 @@ func (client *baseClient) HScan(key string, cursor string) (string, []string, er // // Example: // -// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} -// opts := options.NewHashScanOptionsBuilder().SetMatch("a") -// resCursor, resCollection, err = client.HScan(key, initialCursor, opts) -// // resCursor = {0 false} -// // resCollection = [{a false} {1 false}] -// // The resCollection only contains the hash map entry that matches with the match option provided with the command -// // input. +// // Assume key contains a hash {{"a": "1"}, {"b", "2"}} +// opts := options.NewHashScanOptionsBuilder().SetMatch("a") +// resCursor, resCollection, err = client.HScan(key, initialCursor, opts) +// // resCursor = {0 false} +// // resCollection = [{a false} {1 false}] +// // The resCollection only contains the hash map entry that matches with the match option provided with the command +// // input. // // [valkey.io]: https://valkey.io/commands/hscan/ func (client *baseClient) HScanWithOptions( @@ -1631,8 +1631,8 @@ func (client *Command) SRem(key string, members []string) (int64, error) { // // Parameters: // -// destination - The key of the destination set. -// keys - The keys from which to retrieve the set members. +// destination - The key of the destination set. +// keys - The keys from which to retrieve the set members. // // Return value: // @@ -1847,8 +1847,8 @@ func (client *baseClient) SInter(keys []string) (map[string]struct{}, error) { // // Parameters: // -// destination - The key of the destination set. -// keys - The keys from which to retrieve the set members. +// destination - The key of the destination set. +// keys - The keys from which to retrieve the set members. // // Return value: // @@ -2024,14 +2024,14 @@ func (client *Command) SPop(key string) (Result[string], error) { // // Example: // -// client.SAdd("myKey", []string{"one", "two"}) -// value1, err := client.SMIsMember("myKey", []string{"two", "three"}) -// // value1[0].Value(): true -// // value1[1].Value(): false -// // err: nil -// value2, err := client.SPop("nonExistingKey", []string{"one"}) -// // value2[0].Value(): false -// // err: nil +// client.SAdd("myKey", []string{"one", "two"}) +// value1, err := client.SMIsMember("myKey", []string{"two", "three"}) +// // value1[0].Value(): true +// // value1[1].Value(): false +// // err: nil +// value2, err := client.SPop("nonExistingKey", []string{"one"}) +// // value2[0].Value(): false +// // err: nil // // [valkey.io]: https://valkey.io/commands/smismember/ func (client *Command) SMIsMember(key string, members []string) ([]bool, error) { @@ -2210,8 +2210,8 @@ func (client *baseClient) SScanWithOptions( // // Example: // -// moved := SMove("set1", "set2", "element") -// fmt.Println(moved.Value()) // Output: true +// moved := SMove("set1", "set2", "element") +// fmt.Println(moved.Value()) // Output: true // // [valkey.io]: https://valkey.io/commands/smove/ func (client *Command) SMove(source string, destination string, member string) (bool, error) { @@ -3566,12 +3566,12 @@ func (client *Command) PfCount(keys []string) (int64, error) { // // Note: // -// In cluster mode, if keys in keys map to different hash slots, the command -// will be split across these slots and executed separately for each. This means the command -// is atomic only at the slot level. If one or more slot-specific requests fail, the entire -// call will return the first encountered error, even though some requests may have succeeded -// while others did not. If this behavior impacts your application logic, consider splitting -// the request into sub-requests per slot to ensure atomicity. +// In cluster mode, if keys in keys map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. // // Parameters: // @@ -3631,12 +3631,12 @@ func (client *baseClient) Type(key string) (string, error) { // // Note: // -// In cluster mode, if keys in keys map to different hash slots, the command -// will be split across these slots and executed separately for each. This means the command -// is atomic only at the slot level. If one or more slot-specific requests fail, the entire -// call will return the first encountered error, even though some requests may have succeeded -// while others did not. If this behavior impacts your application logic, consider splitting -// the request into sub-requests per slot to ensure atomicity. +// In cluster mode, if keys in keys map to different hash slots, the command +// will be split across these slots and executed separately for each. This means the command +// is atomic only at the slot level. If one or more slot-specific requests fail, the entire +// call will return the first encountered error, even though some requests may have succeeded +// while others did not. If this behavior impacts your application logic, consider splitting +// the request into sub-requests per slot to ensure atomicity. // // Parameters: // @@ -3682,11 +3682,11 @@ func (client *Command) Touch(keys []string) (int64, error) { // // Example: // -// result, err := client.Rename([]string{"key", "newkey"}) -// if err != nil { -// // handle error -// } -// fmt.Println(result) // Output: OK +// result, err := client.Rename([]string{"key", "newkey"}) +// if err != nil { +// // handle error +// } +// fmt.Println(result) // Output: OK // // [valkey.io]: https://valkey.io/commands/rename/ func (client *baseClient) Rename(key string, newKey string) (string, error) { @@ -3714,11 +3714,11 @@ func (client *baseClient) Rename(key string, newKey string) (string, error) { // // Example: // -// result, err := client.Renamenx([]string{"key", "newkey"}) -// if err != nil { -// // handle error -// } -// fmt.Println(result) // Output: true +// result, err := client.Renamenx([]string{"key", "newkey"}) +// if err != nil { +// // handle error +// } +// fmt.Println(result) // Output: true // // [valkey.io]: https://valkey.io/commands/renamenx/ func (client *Command) Renamenx(key string, newKey string) (bool, error) { @@ -4650,11 +4650,11 @@ func (client *Command) ZRangeWithScores( // // Example: // -// result, err := client.Persist([]string{"key"}) -// if err != nil { -// // handle error -// } -// fmt.Println(result) // Output: true +// result, err := client.Persist([]string{"key"}) +// if err != nil { +// // handle error +// } +// fmt.Println(result) // Output: true // // [valkey.io]: https://valkey.io/commands/persist/ func (client *Command) Persist(key string) (bool, error) { @@ -4686,9 +4686,9 @@ func (client *Command) Persist(key string) (bool, error) { // membersScores := map[string]float64{"one": 1.0, "two": 2.0, "three": 3.0 } // zAddResult, err := client.ZAdd(key1, membersScores) // zCountRange := options.NewZCountRangeBuilder( -// options.NewInfiniteScoreBoundary(options.NegativeInfinity), -// options.NewInfiniteScoreBoundary(options.PositiveInfinity), -// ) +// options.NewInfiniteScoreBoundary(options.NegativeInfinity), +// options.NewInfiniteScoreBoundary(options.PositiveInfinity), +// ) // zCountResult, err := client.ZCount(key1, zCountRange) // if err != nil { // // Handle err @@ -5496,9 +5496,9 @@ func (client *baseClient) XGroupCreateWithOptions( // // Parameters: // -// key - The key to create. -// ttl - The expiry time (in milliseconds). If 0, the key will persist. -// value - The serialized value to deserialize and assign to key. +// key - The key to create. +// ttl - The expiry time (in milliseconds). If 0, the key will persist. +// value - The serialized value to deserialize and assign to key. // // Return value: // @@ -5522,10 +5522,10 @@ func (client *baseClient) Restore(key string, ttl int64, value string) (Result[s // // Parameters: // -// key - The key to create. -// ttl - The expiry time (in milliseconds). If 0, the key will persist. -// value - The serialized value to deserialize and assign to key. -// restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency +// key - The key to create. +// ttl - The expiry time (in milliseconds). If 0, the key will persist. +// value - The serialized value to deserialize and assign to key. +// restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency // // Return value: // @@ -5571,11 +5571,11 @@ func (client *baseClient) RestoreWithOptions(key string, ttl int64, // // Example: // -// result, err := client.Dump([]string{"key"}) -// if err != nil { -// // handle error -// } -// fmt.Println(result.Value()) // Output: (Serialized Value) +// result, err := client.Dump([]string{"key"}) +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: (Serialized Value) // // [valkey.io]: https://valkey.io/commands/dump/ func (client *baseClient) Dump(key string) (Result[string], error) { @@ -5631,11 +5631,11 @@ func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { // // For example: // -// result, err := client.Echo("Hello World") -// if err != nil { -// // handle error -// } -// fmt.Println(result.Value()) // Output: Hello World +// result, err := client.Echo("Hello World") +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: Hello World // // [valkey.io]: https://valkey.io/commands/echo/ func (client *baseClient) Echo(message string) (Result[string], error) { @@ -6056,7 +6056,7 @@ func (client *baseClient) SortStoreWithOptions( // // Example: // -// //Creates the consumer "myconsumer" in consumer group "mygroup" +// // Creates the consumer "myconsumer" in consumer group "mygroup" // success, err := client.xgroupCreateConsumer("mystream", "mygroup", "myconsumer") // if err == nil && success { // fmt.Println("Consumer created") From a5eda6fb76a24d04e95392fce2adb4a2a98e6a0a Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Mon, 20 Jan 2025 15:42:46 -0800 Subject: [PATCH 18/21] performed rebase to pass ci Signed-off-by: Edward Liang --- go/api/base_client.go | 352 ++++++++--------------- go/api/connection_management_commands.go | 18 -- go/api/generic_cluster_commands.go | 25 -- go/api/generic_commands.go | 22 -- go/api/glide_client.go | 82 ++++++ go/api/glide_cluster_client.go | 27 ++ go/api/hyperloglog_commands.go | 1 - go/api/server_management_commands.go | 48 ---- go/api/stream_commands.go | 1 - go/api/string_commands.go | 1 - 10 files changed, 221 insertions(+), 356 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 1a69fa4416..8215551fb4 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -65,9 +65,6 @@ type baseClient struct { coreClient unsafe.Pointer } -// This is a type which allows for pkgsite to identify functions for documentation. -type Command = baseClient - // Creates a connection by invoking the `create_client` function from Rust library via FFI. // Passes the pointers to callback functions which will be invoked when the command succeeds or fails. // Once the connection is established, this function invokes `free_connection_response` exposed by rust library to free the @@ -201,7 +198,7 @@ func toCStrings(args []string) ([]C.uintptr_t, []C.ulong) { // result: "OK" // // [valkey.io]: https://valkey.io/commands/set/ -func (client *Command) Set(key string, value string) (string, error) { +func (client *baseClient) Set(key string, value string) (string, error) { result, err := client.executeCommand(C.Set, []string{key, value}) if err != nil { return defaultStringResponse, err @@ -242,7 +239,7 @@ func (client *Command) Set(key string, value string) (string, error) { // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/set/ -func (client *Command) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) { +func (client *baseClient) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) { optionArgs, err := options.toArgs() if err != nil { return CreateNilStringResult(), err @@ -279,7 +276,7 @@ func (client *Command) SetWithOptions(key string, value string, options *SetOpti // result.IsNil(): true // // [valkey.io]: https://valkey.io/commands/get/ -func (client *Command) Get(key string) (Result[string], error) { +func (client *baseClient) Get(key string) (Result[string], error) { result, err := client.executeCommand(C.Get, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -311,7 +308,7 @@ func (client *Command) Get(key string) (Result[string], error) { // result.IsNil(): true // // [valkey.io]: https://valkey.io/commands/getex/ -func (client *Command) GetEx(key string) (Result[string], error) { +func (client *baseClient) GetEx(key string) (Result[string], error) { result, err := client.executeCommand(C.GetEx, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -345,7 +342,7 @@ func (client *Command) GetEx(key string) (Result[string], error) { // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/getex/ -func (client *Command) GetExWithOptions(key string, options *GetExOptions) (Result[string], error) { +func (client *baseClient) GetExWithOptions(key string, options *GetExOptions) (Result[string], error) { optionArgs, err := options.toArgs() if err != nil { return CreateNilStringResult(), err @@ -384,7 +381,7 @@ func (client *Command) GetExWithOptions(key string, options *GetExOptions) (Resu // result: "OK" // // [valkey.io]: https://valkey.io/commands/mset/ -func (client *Command) MSet(keyValueMap map[string]string) (string, error) { +func (client *baseClient) MSet(keyValueMap map[string]string) (string, error) { result, err := client.executeCommand(C.MSet, utils.MapToString(keyValueMap)) if err != nil { return defaultStringResponse, err @@ -415,15 +412,13 @@ func (client *Command) MSet(keyValueMap map[string]string) (string, error) { // // For example: // 1. result, err := client.MSetNX(map[string]string{"key1": "value1", "key2": "value2"}) -// result.Value(): true -// result.IsNil(): false +// result: true // 2. key3: initialValue // result, err := client.MSetNX(map[string]string{"key3": "value3", "key4": "value4"}) -// result.Value(): false -// result.IsNil(): false +// result: false // // [valkey.io]: https://valkey.io/commands/msetnx/ -func (client *Command) MSetNX(keyValueMap map[string]string) (bool, error) { +func (client *baseClient) MSetNX(keyValueMap map[string]string) (bool, error) { result, err := client.executeCommand(C.MSetNX, utils.MapToString(keyValueMap)) if err != nil { return defaultBoolResponse, err @@ -463,7 +458,7 @@ func (client *Command) MSetNX(keyValueMap map[string]string) (bool, error) { // } // // [valkey.io]: https://valkey.io/commands/mget/ -func (client *Command) MGet(keys []string) ([]Result[string], error) { +func (client *baseClient) MGet(keys []string) ([]Result[string], error) { result, err := client.executeCommand(C.MGet, keys) if err != nil { return nil, err @@ -491,7 +486,7 @@ func (client *Command) MGet(keys []string) ([]Result[string], error) { // result: 2 // // [valkey.io]: https://valkey.io/commands/incr/ -func (client *Command) Incr(key string) (int64, error) { +func (client *baseClient) Incr(key string) (int64, error) { result, err := client.executeCommand(C.Incr, []string{key}) if err != nil { return defaultIntResponse, err @@ -520,7 +515,7 @@ func (client *Command) Incr(key string) (int64, error) { // result: 3 // // [valkey.io]: https://valkey.io/commands/incrby/ -func (client *Command) IncrBy(key string, amount int64) (int64, error) { +func (client *baseClient) IncrBy(key string, amount int64) (int64, error) { result, err := client.executeCommand(C.IncrBy, []string{key, utils.IntToString(amount)}) if err != nil { return defaultIntResponse, err @@ -551,7 +546,7 @@ func (client *Command) IncrBy(key string, amount int64) (int64, error) { // result: 1.5 // // [valkey.io]: https://valkey.io/commands/incrbyfloat/ -func (client *Command) IncrByFloat(key string, amount float64) (float64, error) { +func (client *baseClient) IncrByFloat(key string, amount float64) (float64, error) { result, err := client.executeCommand( C.IncrByFloat, []string{key, utils.FloatToString(amount)}, @@ -582,7 +577,7 @@ func (client *Command) IncrByFloat(key string, amount float64) (float64, error) // result: 0 // // [valkey.io]: https://valkey.io/commands/decr/ -func (client *Command) Decr(key string) (int64, error) { +func (client *baseClient) Decr(key string) (int64, error) { result, err := client.executeCommand(C.Decr, []string{key}) if err != nil { return defaultIntResponse, err @@ -611,7 +606,7 @@ func (client *Command) Decr(key string) (int64, error) { // result: -1 // // [valkey.io]: https://valkey.io/commands/decrby/ -func (client *Command) DecrBy(key string, amount int64) (int64, error) { +func (client *baseClient) DecrBy(key string, amount int64) (int64, error) { result, err := client.executeCommand(C.DecrBy, []string{key, utils.IntToString(amount)}) if err != nil { return defaultIntResponse, err @@ -640,7 +635,7 @@ func (client *Command) DecrBy(key string, amount int64) (int64, error) { // result: 5 // // [valkey.io]: https://valkey.io/commands/strlen/ -func (client *Command) Strlen(key string) (int64, error) { +func (client *baseClient) Strlen(key string) (int64, error) { result, err := client.executeCommand(C.Strlen, []string{key}) if err != nil { return defaultIntResponse, err @@ -676,7 +671,7 @@ func (client *Command) Strlen(key string) (int64, error) { // result.Value(): �a� // (becomes an invalid UTF-8 string) // // [valkey.io]: https://valkey.io/commands/setrange/ -func (client *Command) SetRange(key string, offset int, value string) (int64, error) { +func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { result, err := client.executeCommand(C.SetRange, []string{key, strconv.Itoa(offset), value}) if err != nil { return defaultIntResponse, err @@ -713,7 +708,7 @@ func (client *Command) SetRange(key string, offset int, value string) (int64, er // result: "�" (returns an invalid UTF-8 string) // // [valkey.io]: https://valkey.io/commands/getrange/ -func (client *Command) GetRange(key string, start int, end int) (string, error) { +func (client *baseClient) GetRange(key string, start int, end int) (string, error) { result, err := client.executeCommand(C.GetRange, []string{key, strconv.Itoa(start), strconv.Itoa(end)}) if err != nil { return defaultStringResponse, err @@ -742,7 +737,7 @@ func (client *Command) GetRange(key string, start int, end int) (string, error) // result: 5 // // [valkey.io]: https://valkey.io/commands/append/ -func (client *Command) Append(key string, value string) (int64, error) { +func (client *baseClient) Append(key string, value string) (int64, error) { result, err := client.executeCommand(C.Append, []string{key, value}) if err != nil { return defaultIntResponse, err @@ -783,7 +778,7 @@ func (client *Command) Append(key string, value string) (int64, error) { // result: "fo" // // [valkey.io]: https://valkey.io/commands/lcs/ -func (client *Command) LCS(key1 string, key2 string) (string, error) { +func (client *baseClient) LCS(key1 string, key2 string) (string, error) { result, err := client.executeCommand(C.LCS, []string{key1, key2}) if err != nil { return defaultStringResponse, err @@ -808,7 +803,7 @@ func (client *Command) LCS(key1 string, key2 string) (string, error) { // result, err := client.GetDel("key") // // [valkey.io]: https://valkey.io/commands/getdel/ -func (client *Command) GetDel(key string) (Result[string], error) { +func (client *baseClient) GetDel(key string) (Result[string], error) { if key == "" { return CreateNilStringResult(), errors.New("key is required") } @@ -845,7 +840,7 @@ func (client *Command) GetDel(key string) (Result[string], error) { // // payload equals api.CreateNilStringResult() // // [valkey.io]: https://valkey.io/commands/hget/ -func (client *Command) HGet(key string, field string) (Result[string], error) { +func (client *baseClient) HGet(key string, field string) (Result[string], error) { result, err := client.executeCommand(C.HGet, []string{key, field}) if err != nil { return CreateNilStringResult(), err @@ -907,7 +902,7 @@ func (client *baseClient) HGetAll(key string) (map[string]string, error) { // // values equals []api.Result[string]{value1, value2} // // [valkey.io]: https://valkey.io/commands/hmget/ -func (client *Command) HMGet(key string, fields []string) ([]Result[string], error) { +func (client *baseClient) HMGet(key string, fields []string) ([]Result[string], error) { result, err := client.executeCommand(C.HMGet, append([]string{key}, fields...)) if err != nil { return nil, err @@ -937,7 +932,7 @@ func (client *Command) HMGet(key string, fields []string) ([]Result[string], err // // num: 2 // // [valkey.io]: https://valkey.io/commands/hset/ -func (client *Command) HSet(key string, values map[string]string) (int64, error) { +func (client *baseClient) HSet(key string, values map[string]string) (int64, error) { result, err := client.executeCommand(C.HSet, utils.ConvertMapToKeyValueStringArray(key, values)) if err != nil { return defaultIntResponse, err @@ -973,7 +968,7 @@ func (client *Command) HSet(key string, values map[string]string) (int64, error) // // payload2.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hsetnx/ -func (client *Command) HSetNX(key string, field string, value string) (bool, error) { +func (client *baseClient) HSetNX(key string, field string, value string) (bool, error) { result, err := client.executeCommand(C.HSetNX, []string{key, field, value}) if err != nil { return defaultBoolResponse, err @@ -1002,7 +997,7 @@ func (client *Command) HSetNX(key string, field string, value string) (bool, err // // num: 2 // // [valkey.io]: https://valkey.io/commands/hdel/ -func (client *Command) HDel(key string, fields []string) (int64, error) { +func (client *baseClient) HDel(key string, fields []string) (int64, error) { result, err := client.executeCommand(C.HDel, append([]string{key}, fields...)) if err != nil { return defaultIntResponse, err @@ -1032,7 +1027,7 @@ func (client *Command) HDel(key string, fields []string) (int64, error) { // // num: 0 // // [valkey.io]: https://valkey.io/commands/hlen/ -func (client *Command) HLen(key string) (int64, error) { +func (client *baseClient) HLen(key string) (int64, error) { result, err := client.executeCommand(C.HLen, []string{key}) if err != nil { return defaultIntResponse, err @@ -1092,7 +1087,7 @@ func (client *baseClient) HVals(key string) ([]string, error) { // // exists.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hexists/ -func (client *Command) HExists(key string, field string) (bool, error) { +func (client *baseClient) HExists(key string, field string) (bool, error) { result, err := client.executeCommand(C.HExists, []string{key, field}) if err != nil { return defaultBoolResponse, err @@ -1149,7 +1144,7 @@ func (client *baseClient) HKeys(key string) ([]string, error) { // // strlen.IsNil(): false // // [valkey.io]: https://valkey.io/commands/hstrlen/ -func (client *Command) HStrLen(key string, field string) (int64, error) { +func (client *baseClient) HStrLen(key string, field string) (int64, error) { result, err := client.executeCommand(C.HStrlen, []string{key, field}) if err != nil { return defaultIntResponse, err @@ -1181,7 +1176,7 @@ func (client *Command) HStrLen(key string, field string) (int64, error) { // // hincrByResult: 11 // // [valkey.io]: https://valkey.io/commands/hincrby/ -func (client *Command) HIncrBy(key string, field string, increment int64) (int64, error) { +func (client *baseClient) HIncrBy(key string, field string, increment int64) (int64, error) { result, err := client.executeCommand(C.HIncrBy, []string{key, field, utils.IntToString(increment)}) if err != nil { return defaultIntResponse, err @@ -1213,7 +1208,7 @@ func (client *Command) HIncrBy(key string, field string, increment int64) (int64 // // hincrByFloatResult: 11.5 // // [valkey.io]: https://valkey.io/commands/hincrbyfloat/ -func (client *Command) HIncrByFloat(key string, field string, increment float64) (float64, error) { +func (client *baseClient) HIncrByFloat(key string, field string, increment float64) (float64, error) { result, err := client.executeCommand(C.HIncrByFloat, []string{key, field, utils.FloatToString(increment)}) if err != nil { return defaultFloatResponse, err @@ -1324,7 +1319,7 @@ func (client *baseClient) HScanWithOptions( // result: 2 // // [valkey.io]: https://valkey.io/commands/lpush/ -func (client *Command) LPush(key string, elements []string) (int64, error) { +func (client *baseClient) LPush(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.LPush, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -1357,7 +1352,7 @@ func (client *Command) LPush(key string, elements []string) (int64, error) { // result.IsNil(); true // // [valkey.io]: https://valkey.io/commands/lpop/ -func (client *Command) LPop(key string) (Result[string], error) { +func (client *baseClient) LPop(key string) (Result[string], error) { result, err := client.executeCommand(C.LPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -1418,7 +1413,7 @@ func (client *baseClient) LPopCount(key string, count int64) ([]string, error) { // position.IsNil(): false // // [valkey.io]: https://valkey.io/commands/lpos/ -func (client *Command) LPos(key string, element string) (Result[int64], error) { +func (client *baseClient) LPos(key string, element string) (Result[int64], error) { result, err := client.executeCommand(C.LPos, []string{key, element}) if err != nil { return CreateNilInt64Result(), err @@ -1451,7 +1446,7 @@ func (client *Command) LPos(key string, element string) (Result[int64], error) { // result.Value(): 4 // // [valkey.io]: https://valkey.io/commands/lpos/ -func (client *Command) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) { +func (client *baseClient) LPosWithOptions(key string, element string, options *LPosOptions) (Result[int64], error) { result, err := client.executeCommand(C.LPos, append([]string{key, element}, options.toArgs()...)) if err != nil { return CreateNilInt64Result(), err @@ -1520,7 +1515,7 @@ func (client *baseClient) LPosCount(key string, element string, count int64) ([] // result: []int64{ 5, 6 } // // [valkey.io]: https://valkey.io/commands/lpos/ -func (client *Command) LPosCountWithOptions( +func (client *baseClient) LPosCountWithOptions( key string, element string, count int64, @@ -1558,7 +1553,7 @@ func (client *Command) LPosCountWithOptions( // result: 7 // // [valkey.io]: https://valkey.io/commands/rpush/ -func (client *Command) RPush(key string, elements []string) (int64, error) { +func (client *baseClient) RPush(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.RPush, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -1586,7 +1581,7 @@ func (client *Command) RPush(key string, elements []string) (int64, error) { // // result: 2 // // [valkey.io]: https://valkey.io/commands/sadd/ -func (client *Command) SAdd(key string, members []string) (int64, error) { +func (client *baseClient) SAdd(key string, members []string) (int64, error) { result, err := client.executeCommand(C.SAdd, append([]string{key}, members...)) if err != nil { return defaultIntResponse, err @@ -1614,7 +1609,7 @@ func (client *Command) SAdd(key string, members []string) (int64, error) { // // result: 2 // // [valkey.io]: https://valkey.io/commands/srem/ -func (client *Command) SRem(key string, members []string) (int64, error) { +func (client *baseClient) SRem(key string, members []string) (int64, error) { result, err := client.executeCommand(C.SRem, append([]string{key}, members...)) if err != nil { return defaultIntResponse, err @@ -1647,7 +1642,7 @@ func (client *Command) SRem(key string, members []string) (int64, error) { // // Output: 2 - Two elements were stored at "my_set", and those elements are the union of "set1" and "set2". // // [valkey.io]: https://valkey.io/commands/sunionstore/ -func (client *Command) SUnionStore(destination string, keys []string) (int64, error) { +func (client *baseClient) SUnionStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SUnionStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -1703,7 +1698,7 @@ func (client *baseClient) SMembers(key string) (map[string]struct{}, error) { // // result: 3 // // [valkey.io]: https://valkey.io/commands/scard/ -func (client *Command) SCard(key string) (int64, error) { +func (client *baseClient) SCard(key string) (int64, error) { result, err := client.executeCommand(C.SCard, []string{key}) if err != nil { return defaultIntResponse, err @@ -1736,7 +1731,7 @@ func (client *Command) SCard(key string) (int64, error) { // // Indicates that "nonExistingMember" does not exist in the set "mySet". // // [valkey.io]: https://valkey.io/commands/sismember/ -func (client *Command) SIsMember(key string, member string) (bool, error) { +func (client *baseClient) SIsMember(key string, member string) (bool, error) { result, err := client.executeCommand(C.SIsMember, []string{key, member}) if err != nil { return defaultBoolResponse, err @@ -1799,7 +1794,7 @@ func (client *baseClient) SDiff(keys []string) (map[string]struct{}, error) { // // Indicates that the resulting set "mySet" contains 5 elements // // [valkey.io]: https://valkey.io/commands/sdiffstore/ -func (client *Command) SDiffStore(destination string, keys []string) (int64, error) { +func (client *baseClient) SDiffStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SDiffStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -1863,7 +1858,7 @@ func (client *baseClient) SInter(keys []string) (map[string]struct{}, error) { // // Output: 2 - Two elements were stored at "my_set", and those elements are the intersection of "set1" and "set2". // // [valkey.io]: https://valkey.io/commands/sinterstore/ -func (client *Command) SInterStore(destination string, keys []string) (int64, error) { +func (client *baseClient) SInterStore(destination string, keys []string) (int64, error) { result, err := client.executeCommand(C.SInterStore, append([]string{destination}, keys...)) if err != nil { return defaultIntResponse, err @@ -1899,7 +1894,7 @@ func (client *Command) SInterStore(destination string, keys []string) (int64, er // // result: 0 // // [valkey.io]: https://valkey.io/commands/sintercard/ -func (client *Command) SInterCard(keys []string) (int64, error) { +func (client *baseClient) SInterCard(keys []string) (int64, error) { result, err := client.executeCommand(C.SInterCard, append([]string{strconv.Itoa(len(keys))}, keys...)) if err != nil { return defaultIntResponse, err @@ -1937,7 +1932,7 @@ func (client *Command) SInterCard(keys []string) (int64, error) { // // intersection is larger) // // [valkey.io]: https://valkey.io/commands/sintercard/ -func (client *Command) SInterCardLimit(keys []string, limit int64) (int64, error) { +func (client *baseClient) SInterCardLimit(keys []string, limit int64) (int64, error) { args := utils.Concat([]string{utils.IntToString(int64(len(keys)))}, keys, []string{"LIMIT", utils.IntToString(limit)}) result, err := client.executeCommand(C.SInterCard, args) @@ -1969,7 +1964,7 @@ func (client *Command) SInterCardLimit(keys []string, limit int64) (int64, error // // err: nil // // [valkey.io]: https://valkey.io/commands/srandmember/ -func (client *Command) SRandMember(key string) (Result[string], error) { +func (client *baseClient) SRandMember(key string) (Result[string], error) { result, err := client.executeCommand(C.SRandMember, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -2001,7 +1996,7 @@ func (client *Command) SRandMember(key string) (Result[string], error) { // // err: nil // // [valkey.io]: https://valkey.io/commands/spop/ -func (client *Command) SPop(key string) (Result[string], error) { +func (client *baseClient) SPop(key string) (Result[string], error) { result, err := client.executeCommand(C.SPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -2034,7 +2029,7 @@ func (client *Command) SPop(key string) (Result[string], error) { // // err: nil // // [valkey.io]: https://valkey.io/commands/smismember/ -func (client *Command) SMIsMember(key string, members []string) ([]bool, error) { +func (client *baseClient) SMIsMember(key string, members []string) ([]bool, error) { result, err := client.executeCommand(C.SMIsMember, append([]string{key}, members...)) if err != nil { return nil, err @@ -2214,7 +2209,7 @@ func (client *baseClient) SScanWithOptions( // fmt.Println(moved.Value()) // Output: true // // [valkey.io]: https://valkey.io/commands/smove/ -func (client *Command) SMove(source string, destination string, member string) (bool, error) { +func (client *baseClient) SMove(source string, destination string, member string) (bool, error) { result, err := client.executeCommand(C.SMove, []string{source, destination, member}) if err != nil { return defaultBoolResponse, err @@ -2286,7 +2281,7 @@ func (client *baseClient) LRange(key string, start int64, end int64) ([]string, // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/lindex/ -func (client *Command) LIndex(key string, index int64) (Result[string], error) { +func (client *baseClient) LIndex(key string, index int64) (Result[string], error) { result, err := client.executeCommand(C.LIndex, []string{key, utils.IntToString(index)}) if err != nil { return CreateNilStringResult(), err @@ -2322,7 +2317,7 @@ func (client *Command) LIndex(key string, index int64) (Result[string], error) { // result: "OK" // // [valkey.io]: https://valkey.io/commands/ltrim/ -func (client *Command) LTrim(key string, start int64, end int64) (string, error) { +func (client *baseClient) LTrim(key string, start int64, end int64) (string, error) { result, err := client.executeCommand(C.LTrim, []string{key, utils.IntToString(start), utils.IntToString(end)}) if err != nil { return defaultStringResponse, err @@ -2350,7 +2345,7 @@ func (client *Command) LTrim(key string, start int64, end int64) (string, error) // result: 3 // Indicates that there are 3 elements in the list. // // [valkey.io]: https://valkey.io/commands/llen/ -func (client *Command) LLen(key string) (int64, error) { +func (client *baseClient) LLen(key string) (int64, error) { result, err := client.executeCommand(C.LLen, []string{key}) if err != nil { return defaultIntResponse, err @@ -2384,7 +2379,7 @@ func (client *Command) LLen(key string) (int64, error) { // result: 2 // // [valkey.io]: https://valkey.io/commands/lrem/ -func (client *Command) LRem(key string, count int64, element string) (int64, error) { +func (client *baseClient) LRem(key string, count int64, element string) (int64, error) { result, err := client.executeCommand(C.LRem, []string{key, utils.IntToString(count), element}) if err != nil { return defaultIntResponse, err @@ -2416,7 +2411,7 @@ func (client *Command) LRem(key string, count int64, element string) (int64, err // result.IsNil(): true // // [valkey.io]: https://valkey.io/commands/rpop/ -func (client *Command) RPop(key string) (Result[string], error) { +func (client *baseClient) RPop(key string) (Result[string], error) { result, err := client.executeCommand(C.RPop, []string{key}) if err != nil { return CreateNilStringResult(), err @@ -2479,7 +2474,7 @@ func (client *baseClient) RPopCount(key string, count int64) ([]string, error) { // result: 3 // // [valkey.io]: https://valkey.io/commands/linsert/ -func (client *Command) LInsert( +func (client *baseClient) LInsert( key string, insertPosition InsertPosition, pivot string, @@ -2596,7 +2591,7 @@ func (client *baseClient) BRPop(keys []string, timeoutSecs float64) ([]string, e // result: 4 // // [valkey.io]: https://valkey.io/commands/rpushx/ -func (client *Command) RPushX(key string, elements []string) (int64, error) { +func (client *baseClient) RPushX(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.RPushX, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -2626,7 +2621,7 @@ func (client *Command) RPushX(key string, elements []string) (int64, error) { // result: 4 // // [valkey.io]: https://valkey.io/commands/rpushx/ -func (client *Command) LPushX(key string, elements []string) (int64, error) { +func (client *baseClient) LPushX(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.LPushX, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -2708,7 +2703,7 @@ func (client *baseClient) LMPop(keys []string, listDirection ListDirection) (map // result["my_list"] = []string{"three"} // // [valkey.io]: https://valkey.io/commands/lmpop/ -func (client *Command) LMPopCount( +func (client *baseClient) LMPopCount( keys []string, listDirection ListDirection, count int64, @@ -2769,7 +2764,7 @@ func (client *Command) LMPopCount( // // [valkey.io]: https://valkey.io/commands/blmpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BLMPop( +func (client *baseClient) BLMPop( keys []string, listDirection ListDirection, timeoutSecs float64, @@ -2832,7 +2827,7 @@ func (client *Command) BLMPop( // // [valkey.io]: https://valkey.io/commands/blmpop/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BLMPopCount( +func (client *baseClient) BLMPopCount( keys []string, listDirection ListDirection, count int64, @@ -2884,7 +2879,7 @@ func (client *Command) BLMPopCount( // result: "OK" // // [valkey.io]: https://valkey.io/commands/lset/ -func (client *Command) LSet(key string, index int64, element string) (string, error) { +func (client *baseClient) LSet(key string, index int64, element string) (string, error) { result, err := client.executeCommand(C.LSet, []string{key, utils.IntToString(index), element}) if err != nil { return defaultStringResponse, err @@ -2921,7 +2916,7 @@ func (client *Command) LSet(key string, index int64, element string) (string, er // updatedList2: []string{ "one", "three", "four" } // // [valkey.io]: https://valkey.io/commands/lmove/ -func (client *Command) LMove( +func (client *baseClient) LMove( source string, destination string, whereFrom ListDirection, @@ -2984,7 +2979,7 @@ func (client *Command) LMove( // // [valkey.io]: https://valkey.io/commands/blmove/ // [Blocking Commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BLMove( +func (client *baseClient) BLMove( source string, destination string, whereFrom ListDirection, @@ -3022,7 +3017,7 @@ func (client *Command) BLMove( // result, err := client.Ping() // // [valkey.io]: https://valkey.io/commands/ping/ -func (client *Command) Ping() (string, error) { +func (client *baseClient) Ping() (string, error) { result, err := client.executeCommand(C.Ping, []string{}) if err != nil { return defaultStringResponse, err @@ -3046,7 +3041,7 @@ func (client *Command) Ping() (string, error) { // result, err := client.PingWithMessage("Hello") // // [valkey.io]: https://valkey.io/commands/ping/ -func (client *Command) PingWithMessage(message string) (string, error) { +func (client *baseClient) PingWithMessage(message string) (string, error) { args := []string{message} result, err := client.executeCommand(C.Ping, args) @@ -3085,7 +3080,7 @@ func (client *Command) PingWithMessage(message string) (string, error) { // fmt.Println(result) // Output: 2 // // [valkey.io]: https://valkey.io/commands/del/ -func (client *Command) Del(keys []string) (int64, error) { +func (client *baseClient) Del(keys []string) (int64, error) { result, err := client.executeCommand(C.Del, keys) if err != nil { return defaultIntResponse, err @@ -3116,7 +3111,7 @@ func (client *Command) Del(keys []string) (int64, error) { // result: 2 // // [valkey.io]: https://valkey.io/commands/exists/ -func (client *Command) Exists(keys []string) (int64, error) { +func (client *baseClient) Exists(keys []string) (int64, error) { result, err := client.executeCommand(C.Exists, keys) if err != nil { return defaultIntResponse, err @@ -3145,7 +3140,7 @@ func (client *Command) Exists(keys []string) (int64, error) { // result: true // // [valkey.io]: https://valkey.io/commands/expire/ -func (client *Command) Expire(key string, seconds int64) (bool, error) { +func (client *baseClient) Expire(key string, seconds int64) (bool, error) { result, err := client.executeCommand(C.Expire, []string{key, utils.IntToString(seconds)}) if err != nil { return defaultBoolResponse, err @@ -3175,7 +3170,7 @@ func (client *Command) Expire(key string, seconds int64) (bool, error) { // result: true // // [valkey.io]: https://valkey.io/commands/expire/ -func (client *Command) ExpireWithOptions(key string, seconds int64, expireCondition ExpireCondition) (bool, error) { +func (client *baseClient) ExpireWithOptions(key string, seconds int64, expireCondition ExpireCondition) (bool, error) { expireConditionStr, err := expireCondition.toString() if err != nil { return defaultBoolResponse, err @@ -3210,7 +3205,7 @@ func (client *Command) ExpireWithOptions(key string, seconds int64, expireCondit // result: true // // [valkey.io]: https://valkey.io/commands/expireat/ -func (client *Command) ExpireAt(key string, unixTimestampInSeconds int64) (bool, error) { +func (client *baseClient) ExpireAt(key string, unixTimestampInSeconds int64) (bool, error) { result, err := client.executeCommand(C.ExpireAt, []string{key, utils.IntToString(unixTimestampInSeconds)}) if err != nil { return defaultBoolResponse, err @@ -3243,7 +3238,7 @@ func (client *Command) ExpireAt(key string, unixTimestampInSeconds int64) (bool, // result: true // // [valkey.io]: https://valkey.io/commands/expireat/ -func (client *Command) ExpireAtWithOptions( +func (client *baseClient) ExpireAtWithOptions( key string, unixTimestampInSeconds int64, expireCondition ExpireCondition, @@ -3281,7 +3276,7 @@ func (client *Command) ExpireAtWithOptions( // result: true // // [valkey.io]: https://valkey.io/commands/pexpire/ -func (client *Command) PExpire(key string, milliseconds int64) (bool, error) { +func (client *baseClient) PExpire(key string, milliseconds int64) (bool, error) { result, err := client.executeCommand(C.PExpire, []string{key, utils.IntToString(milliseconds)}) if err != nil { return defaultBoolResponse, err @@ -3309,7 +3304,7 @@ func (client *Command) PExpire(key string, milliseconds int64) (bool, error) { // result: true // // [valkey.io]: https://valkey.io/commands/pexpire/ -func (client *Command) PExpireWithOptions( +func (client *baseClient) PExpireWithOptions( key string, milliseconds int64, expireCondition ExpireCondition, @@ -3347,7 +3342,7 @@ func (client *Command) PExpireWithOptions( // result: true // // [valkey.io]: https://valkey.io/commands/pexpireat/ -func (client *Command) PExpireAt(key string, unixTimestampInMilliSeconds int64) (bool, error) { +func (client *baseClient) PExpireAt(key string, unixTimestampInMilliSeconds int64) (bool, error) { result, err := client.executeCommand(C.PExpireAt, []string{key, utils.IntToString(unixTimestampInMilliSeconds)}) if err != nil { return defaultBoolResponse, err @@ -3378,7 +3373,7 @@ func (client *Command) PExpireAt(key string, unixTimestampInMilliSeconds int64) // result: true // // [valkey.io]: https://valkey.io/commands/pexpireat/ -func (client *Command) PExpireAtWithOptions( +func (client *baseClient) PExpireAtWithOptions( key string, unixTimestampInMilliSeconds int64, expireCondition ExpireCondition, @@ -3413,7 +3408,7 @@ func (client *Command) PExpireAtWithOptions( // result: 1732118030 // // [valkey.io]: https://valkey.io/commands/expiretime/ -func (client *Command) ExpireTime(key string) (int64, error) { +func (client *baseClient) ExpireTime(key string) (int64, error) { result, err := client.executeCommand(C.ExpireTime, []string{key}) if err != nil { return defaultIntResponse, err @@ -3438,7 +3433,7 @@ func (client *Command) ExpireTime(key string) (int64, error) { // result: 33177117420000 // // [valkey.io]: https://valkey.io/commands/pexpiretime/ -func (client *Command) PExpireTime(key string) (int64, error) { +func (client *baseClient) PExpireTime(key string) (int64, error) { result, err := client.executeCommand(C.PExpireTime, []string{key}) if err != nil { return defaultIntResponse, err @@ -3462,7 +3457,7 @@ func (client *Command) PExpireTime(key string) (int64, error) { // result: 3 // // [valkey.io]: https://valkey.io/commands/ttl/ -func (client *Command) TTL(key string) (int64, error) { +func (client *baseClient) TTL(key string) (int64, error) { result, err := client.executeCommand(C.TTL, []string{key}) if err != nil { return defaultIntResponse, err @@ -3486,7 +3481,7 @@ func (client *Command) TTL(key string) (int64, error) { // result: 1000 // // [valkey.io]: https://valkey.io/commands/pttl/ -func (client *Command) PTTL(key string) (int64, error) { +func (client *baseClient) PTTL(key string) (int64, error) { result, err := client.executeCommand(C.PTTL, []string{key}) if err != nil { return defaultIntResponse, err @@ -3516,7 +3511,7 @@ func (client *Command) PTTL(key string) (int64, error) { // result: 1 // // [valkey.io]: https://valkey.io/commands/pfadd/ -func (client *Command) PfAdd(key string, elements []string) (int64, error) { +func (client *baseClient) PfAdd(key string, elements []string) (int64, error) { result, err := client.executeCommand(C.PfAdd, append([]string{key}, elements...)) if err != nil { return defaultIntResponse, err @@ -3552,7 +3547,7 @@ func (client *Command) PfAdd(key string, elements []string) (int64, error) { // result: 5 // // [valkey.io]: https://valkey.io/commands/pfcount/ -func (client *Command) PfCount(keys []string) (int64, error) { +func (client *baseClient) PfCount(keys []string) (int64, error) { result, err := client.executeCommand(C.PfCount, keys) if err != nil { return defaultIntResponse, err @@ -3590,7 +3585,7 @@ func (client *Command) PfCount(keys []string) (int64, error) { // fmt.Println(result) // Output: 3 // // [valkey.io]: Https://valkey.io/commands/unlink/ -func (client *Command) Unlink(keys []string) (int64, error) { +func (client *baseClient) Unlink(keys []string) (int64, error) { result, err := client.executeCommand(C.Unlink, keys) if err != nil { return defaultIntResponse, err @@ -3655,7 +3650,7 @@ func (client *baseClient) Type(key string) (string, error) { // fmt.Println(result) // Output: 3 // // [valkey.io]: Https://valkey.io/commands/touch/ -func (client *Command) Touch(keys []string) (int64, error) { +func (client *baseClient) Touch(keys []string) (int64, error) { result, err := client.executeCommand(C.Touch, keys) if err != nil { return defaultIntResponse, err @@ -3721,7 +3716,7 @@ func (client *baseClient) Rename(key string, newKey string) (string, error) { // fmt.Println(result) // Output: true // // [valkey.io]: https://valkey.io/commands/renamenx/ -func (client *Command) Renamenx(key string, newKey string) (bool, error) { +func (client *baseClient) Renamenx(key string, newKey string) (bool, error) { result, err := client.executeCommand(C.RenameNX, []string{key, newKey}) if err != nil { return defaultBoolResponse, err @@ -3749,7 +3744,7 @@ func (client *Command) Renamenx(key string, newKey string) (bool, error) { // result.Value(): "1526919030474-55" // // [valkey.io]: https://valkey.io/commands/xadd/ -func (client *Command) XAdd(key string, values [][]string) (Result[string], error) { +func (client *baseClient) XAdd(key string, values [][]string) (Result[string], error) { return client.XAddWithOptions(key, values, options.NewXAddOptions()) } @@ -3775,7 +3770,7 @@ func (client *Command) XAdd(key string, values [][]string) (Result[string], erro // result.Value(): "100-500" // // [valkey.io]: https://valkey.io/commands/xadd/ -func (client *Command) XAddWithOptions( +func (client *baseClient) XAddWithOptions( key string, values [][]string, options *options.XAddOptions, @@ -3830,7 +3825,7 @@ func (client *Command) XAddWithOptions( // } // // [valkey.io]: https://valkey.io/commands/xread/ -func (client *Command) XRead(keysAndIds map[string]string) (map[string]map[string][][]string, error) { +func (client *baseClient) XRead(keysAndIds map[string]string) (map[string]map[string][][]string, error) { return client.XReadWithOptions(keysAndIds, options.NewXReadOptions()) } @@ -3862,7 +3857,7 @@ func (client *Command) XRead(keysAndIds map[string]string) (map[string]map[strin // } // // [valkey.io]: https://valkey.io/commands/xread/ -func (client *Command) XReadWithOptions( +func (client *baseClient) XReadWithOptions( keysAndIds map[string]string, options *options.XReadOptions, ) (map[string]map[string][][]string, error) { @@ -4012,129 +4007,6 @@ func createStreamCommandArgs( return args, nil } -// Reads entries from the given streams owned by a consumer group. -// -// Note: -// -// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. -// -// See [valkey.io] for details. -// -// Parameters: -// -// group - The consumer group name. -// consumer - The group consumer. -// keysAndIds - A map of keys and entry IDs to read from. -// -// 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. -// -// For example: -// -// result, err := client.XReadGroup({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}) -// err == nil: true -// result: map[string]map[string][][]string{ -// "stream1": { -// "0-1": {{"field1", "value1"}}, -// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, -// }, -// "stream2": { -// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, -// "1526985685298-0": nil, // entry was deleted -// }, -// "stream3": {}, // stream is empty -// } -// -// [valkey.io]: https://valkey.io/commands/xreadgroup/ -func (client *baseClient) XReadGroup( - group string, - consumer string, - keysAndIds map[string]string, -) (map[string]map[string][][]string, error) { - return client.XReadGroupWithOptions(group, consumer, keysAndIds, options.NewXReadGroupOptions()) -} - -// Reads entries from the given streams owned by a consumer group. -// -// Note: -// -// When in cluster mode, all keys in `keysAndIds` must map to the same hash slot. -// -// See [valkey.io] for details. -// -// Parameters: -// -// group - The consumer group name. -// consumer - The group consumer. -// keysAndIds - A map of keys and entry IDs to read from. -// options - Options detailing how to read the stream. -// -// 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. -// -// For example: -// -// options := options.NewXReadGroupOptions().SetNoAck() -// result, err := client.XReadGroupWithOptions({"stream1": "0-0", "stream2": "0-1", "stream3": "0-1"}, options) -// err == nil: true -// result: map[string]map[string][][]string{ -// "stream1": { -// "0-1": {{"field1", "value1"}}, -// "0-2": {{"field2", "value2"}, {"field2", "value3"}}, -// }, -// "stream2": { -// "1526985676425-0": {{"name", "Virginia"}, {"surname", "Woolf"}}, -// "1526985685298-0": nil, // entry was deleted -// }, -// "stream3": {}, // stream is empty -// } -// -// [valkey.io]: https://valkey.io/commands/xreadgroup/ -func (client *baseClient) XReadGroupWithOptions( - group string, - consumer string, - keysAndIds map[string]string, - options *options.XReadGroupOptions, -) (map[string]map[string][][]string, error) { - args, err := createStreamCommandArgs([]string{"GROUP", group, consumer}, keysAndIds, options) - if err != nil { - return nil, err - } - - result, err := client.executeCommand(C.XReadGroup, args) - if err != nil { - return nil, err - } - - return handleXReadGroupResponse(result) -} - -// Combine `args` with `keysAndIds` and `options` into arguments for a stream command -func createStreamCommandArgs( - args []string, - keysAndIds map[string]string, - options interface{ ToArgs() ([]string, error) }, -) ([]string, error) { - optionArgs, err := options.ToArgs() - if err != nil { - return nil, err - } - args = append(args, optionArgs...) - // Note: this loop iterates in an indeterminate order, but it is OK for that case - keys := make([]string, 0, len(keysAndIds)) - values := make([]string, 0, len(keysAndIds)) - for key := range keysAndIds { - keys = append(keys, key) - values = append(values, keysAndIds[key]) - } - args = append(args, "STREAMS") - args = append(args, keys...) - args = append(args, values...) - return args, nil -} - // Adds one or more members to a sorted set, or updates their scores. Creates the key if it doesn't exist. // // See [valkey.io] for details. @@ -4154,7 +4026,7 @@ func createStreamCommandArgs( // fmt.Println(res) // Output: 3 // // [valkey.io]: https://valkey.io/commands/zadd/ -func (client *Command) ZAdd( +func (client *baseClient) ZAdd( key string, membersScoreMap map[string]float64, ) (int64, error) { @@ -4190,7 +4062,7 @@ func (client *Command) ZAdd( // fmt.Println(res) // Output: 3 // // [valkey.io]: https://valkey.io/commands/zadd/ -func (client *Command) ZAddWithOptions( +func (client *baseClient) ZAddWithOptions( key string, membersScoreMap map[string]float64, opts *options.ZAddOptions, @@ -4245,7 +4117,7 @@ func (client *baseClient) zAddIncrBase(key string, opts *options.ZAddOptions) (R // fmt.Println(res.Value()) // Output: 1.0 // // [valkey.io]: https://valkey.io/commands/zadd/ -func (client *Command) ZAddIncr( +func (client *baseClient) ZAddIncr( key string, member string, increment float64, @@ -4280,7 +4152,7 @@ func (client *Command) ZAddIncr( // fmt.Println(res.Value()) // Output: 1.0 // // [valkey.io]: https://valkey.io/commands/zadd/ -func (client *Command) ZAddIncrWithOptions( +func (client *baseClient) ZAddIncrWithOptions( key string, member string, increment float64, @@ -4317,7 +4189,7 @@ func (client *Command) ZAddIncrWithOptions( // fmt.Println(res) // Output: 2.0 // // [valkey.io]: https://valkey.io/commands/zincrby/ -func (client *Command) ZIncrBy(key string, increment float64, member string) (float64, error) { +func (client *baseClient) ZIncrBy(key string, increment float64, member string) (float64, error) { result, err := client.executeCommand(C.ZIncrBy, []string{key, utils.FloatToString(increment), member}) if err != nil { return defaultFloatResponse, err @@ -4465,7 +4337,7 @@ func (client *baseClient) ZPopMaxWithCount(key string, count int64) (map[string] // fmt.Println(res) // Output: 2 // // [valkey.io]: https://valkey.io/commands/zrem/ -func (client *Command) ZRem(key string, members []string) (int64, error) { +func (client *baseClient) ZRem(key string, members []string) (int64, error) { result, err := client.executeCommand(C.ZRem, append([]string{key}, members...)) if err != nil { return defaultIntResponse, err @@ -4494,7 +4366,7 @@ func (client *Command) ZRem(key string, members []string) (int64, error) { // result: 1 // There is 1 item in the set // // [valkey.io]: https://valkey.io/commands/zcard/ -func (client *Command) ZCard(key string) (int64, error) { +func (client *baseClient) ZCard(key string) (int64, error) { result, err := client.executeCommand(C.ZCard, []string{key}) if err != nil { return defaultIntResponse, err @@ -4535,7 +4407,7 @@ func (client *Command) ZCard(key string) (int64, error) { // [valkey.io]: https://valkey.io/commands/bzpopmin/ // // [blocking commands]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#blocking-commands -func (client *Command) BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error) { +func (client *baseClient) BZPopMin(keys []string, timeoutSecs float64) (Result[KeyWithMemberAndScore], error) { result, err := client.executeCommand(C.BZPopMin, append(keys, utils.FloatToString(timeoutSecs))) if err != nil { return CreateNilKeyWithMemberAndScoreResult(), err @@ -4621,7 +4493,7 @@ func (client *baseClient) ZRange(key string, rangeQuery options.ZRangeQuery) ([] // // `result` contains members with scores within the range of negative infinity to 3, in descending order // // [valkey.io]: https://valkey.io/commands/zrange/ -func (client *Command) ZRangeWithScores( +func (client *baseClient) ZRangeWithScores( key string, rangeQuery options.ZRangeQueryWithScores, ) (map[string]float64, error) { @@ -4657,7 +4529,7 @@ func (client *Command) ZRangeWithScores( // fmt.Println(result) // Output: true // // [valkey.io]: https://valkey.io/commands/persist/ -func (client *Command) Persist(key string) (bool, error) { +func (client *baseClient) Persist(key string) (bool, error) { result, err := client.executeCommand(C.Persist, []string{key}) if err != nil { return defaultBoolResponse, err @@ -4696,7 +4568,7 @@ func (client *Command) Persist(key string) (bool, error) { // fmt.Println(zCountResult) // Output: 3 // // [valkey.io]: https://valkey.io/commands/zcount/ -func (client *Command) ZCount(key string, rangeOptions *options.ZCountRange) (int64, error) { +func (client *baseClient) ZCount(key string, rangeOptions *options.ZCountRange) (int64, error) { zCountRangeArgs, err := rangeOptions.ToArgs() if err != nil { return defaultIntResponse, err @@ -4736,7 +4608,7 @@ func (client *Command) ZCount(key string, rangeOptions *options.ZCountRange) (in // } // // [valkey.io]: https://valkey.io/commands/zrank/ -func (client *Command) ZRank(key string, member string) (Result[int64], error) { +func (client *baseClient) ZRank(key string, member string) (Result[int64], error) { result, err := client.executeCommand(C.ZRank, []string{key, member}) if err != nil { return CreateNilInt64Result(), err @@ -4772,7 +4644,7 @@ func (client *Command) ZRank(key string, member string) (Result[int64], error) { // } // // [valkey.io]: https://valkey.io/commands/zrank/ -func (client *Command) ZRankWithScore(key string, member string) (Result[int64], Result[float64], error) { +func (client *baseClient) ZRankWithScore(key string, member string) (Result[int64], Result[float64], error) { result, err := client.executeCommand(C.ZRank, []string{key, member, options.WithScore}) if err != nil { return CreateNilInt64Result(), CreateNilFloat64Result(), err @@ -4809,7 +4681,7 @@ func (client *Command) ZRankWithScore(key string, member string) (Result[int64], // } // // [valkey.io]: https://valkey.io/commands/zrevrank/ -func (client *Command) ZRevRank(key string, member string) (Result[int64], error) { +func (client *baseClient) ZRevRank(key string, member string) (Result[int64], error) { result, err := client.executeCommand(C.ZRevRank, []string{key, member}) if err != nil { return CreateNilInt64Result(), err @@ -4846,7 +4718,7 @@ func (client *Command) ZRevRank(key string, member string) (Result[int64], error // } // // [valkey.io]: https://valkey.io/commands/zrevrank/ -func (client *Command) ZRevRankWithScore(key string, member string) (Result[int64], Result[float64], error) { +func (client *baseClient) ZRevRankWithScore(key string, member string) (Result[int64], Result[float64], error) { result, err := client.executeCommand(C.ZRevRank, []string{key, member, options.WithScore}) if err != nil { return CreateNilInt64Result(), CreateNilFloat64Result(), err @@ -4883,7 +4755,7 @@ func (client *Command) ZRevRankWithScore(key string, member string) (Result[int6 // fmt.Println(xTrimResult) // Output: 1 // // [valkey.io]: https://valkey.io/commands/xtrim/ -func (client *Command) XTrim(key string, options *options.XTrimOptions) (int64, error) { +func (client *baseClient) XTrim(key string, options *options.XTrimOptions) (int64, error) { xTrimArgs, err := options.ToArgs() if err != nil { return defaultIntResponse, err @@ -4920,7 +4792,7 @@ func (client *Command) XTrim(key string, options *options.XTrimOptions) (int64, // fmt.Println(xLenResult) // Output: 2 // // [valkey.io]: https://valkey.io/commands/xlen/ -func (client *Command) XLen(key string) (int64, error) { +func (client *baseClient) XLen(key string) (int64, error) { result, err := client.executeCommand(C.XLen, []string{key}) if err != nil { return defaultIntResponse, err @@ -5204,7 +5076,7 @@ func (client *baseClient) XAutoClaimJustIdWithOptions( // fmt.Println(xDelResult) // Output: 1 // // [valkey.io]: https://valkey.io/commands/xdel/ -func (client *Command) XDel(key string, ids []string) (int64, error) { +func (client *baseClient) XDel(key string, ids []string) (int64, error) { result, err := client.executeCommand(C.XDel, append([]string{key}, ids...)) if err != nil { return defaultIntResponse, err @@ -5631,11 +5503,11 @@ func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { // // For example: // -// result, err := client.Echo("Hello World") -// if err != nil { -// // handle error -// } -// fmt.Println(result.Value()) // Output: Hello World +// result, err := client.Echo("Hello World") +// if err != nil { +// // handle error +// } +// fmt.Println(result.Value()) // Output: Hello World // // [valkey.io]: https://valkey.io/commands/echo/ func (client *baseClient) Echo(message string) (Result[string], error) { diff --git a/go/api/connection_management_commands.go b/go/api/connection_management_commands.go index 59d4c3c372..480b85af91 100644 --- a/go/api/connection_management_commands.go +++ b/go/api/connection_management_commands.go @@ -8,27 +8,9 @@ package api // // [valkey.io]: https://valkey.io/commands/#connection type ConnectionManagementCommands interface { - Ping() (string, error) PingWithMessage(message string) (string, error) - // Echo the provided message back. - // The command will be routed a random node. - // - // Parameters: - // message - The provided message. - // - // Return value: - // The provided message - // - // For example: - // result, err := client.Echo("Hello World") - // if err != nil { - // // handle error - // } - // fmt.Println(result.Value()) // Output: Hello World - // - // [valkey.io]: https://valkey.io/commands/echo/ Echo(message string) (Result[string], error) } diff --git a/go/api/generic_cluster_commands.go b/go/api/generic_cluster_commands.go index cd46fca42b..3d5186caa3 100644 --- a/go/api/generic_cluster_commands.go +++ b/go/api/generic_cluster_commands.go @@ -8,30 +8,5 @@ package api // // [valkey.io]: https://valkey.io/commands/#generic type GenericClusterCommands interface { - // CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, - // including the command name and subcommands, should be added as a separate value in args. The returning value depends on - // the executed - // command. - // - // The command will be routed automatically based on the passed command's default request policy. - // - // See [Valkey GLIDE Wiki] for details on the restrictions and limitations of the custom command API. - // - // This function should only be used for single-response commands. Commands that don't return complete response and awaits - // (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's - // behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. - // - // Parameters: - // args - Arguments for the custom command including the command name. - // - // Return value: - // The returned value for the custom command. - // - // For example: - // - // result, err := client.CustomCommand([]string{"ping"}) - // result.Value().(string): "PONG" - // - // [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command CustomCommand(args []string) (ClusterValue[interface{}], error) } diff --git a/go/api/generic_commands.go b/go/api/generic_commands.go index 5da3b5e5ce..7443c13bcc 100644 --- a/go/api/generic_commands.go +++ b/go/api/generic_commands.go @@ -8,27 +8,5 @@ package api // // [valkey.io]: https://valkey.io/commands/#generic type GenericCommands interface { - // CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, - // including the command name and subcommands, should be added as a separate value in args. The returning value depends on - // the executed - // command. - // - // See [Valkey GLIDE Wiki] for details on the restrictions and limitations of the custom command API. - // - // This function should only be used for single-response commands. Commands that don't return complete response and awaits - // (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's - // behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. - // - // Parameters: - // args - Arguments for the custom command including the command name. - // - // Return value: - // The returned value for the custom command. - // - // For example: - // result, err := client.CustomCommand([]string{"ping"}) - // result.(string): "PONG" - // - // [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command CustomCommand(args []string) (interface{}, error) } diff --git a/go/api/glide_client.go b/go/api/glide_client.go index bb1954145e..985cf9b070 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -35,6 +35,31 @@ func NewGlideClient(config *GlideClientConfiguration) (IGlideClient, error) { return &GlideClient{client}, nil } +// CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, +// including the command name and subcommands, should be added as a separate value in args. The returning value depends on +// the executed +// command. +// +// See [Valkey GLIDE Wiki] for details on the restrictions and limitations of the custom command API. +// +// This function should only be used for single-response commands. Commands that don't return complete response and awaits +// (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's +// behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. +// +// Parameters: +// +// args - Arguments for the custom command including the command name. +// +// Return value: +// +// The returned value for the custom command. +// +// For example: +// +// result, err := client.CustomCommand([]string{"ping"}) +// result.(string): "PONG" +// +// [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { res, err := client.executeCommand(C.CustomCommand, args) if err != nil { @@ -43,6 +68,26 @@ func (client *GlideClient) CustomCommand(args []string) (interface{}, error) { return handleInterfaceResponse(res) } +// Sets configuration parameters to the specified values. +// +// Note: Prior to Version 7.0.0, only one parameter can be send. +// +// See [valkey.io] for details. +// +// Parameters: +// +// parameters - A map consisting of configuration parameters and their respective values to set. +// +// Return value: +// +// `"OK"` if all configurations have been successfully set. Otherwise, raises an error. +// +// For example: +// +// result, err := client.ConfigSet(map[string]string{"timeout": "1000", "maxmemory": "1GB"}) +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/config-set/ func (client *GlideClient) ConfigSet(parameters map[string]string) (string, error) { result, err := client.executeCommand(C.ConfigSet, utils.MapToString(parameters)) if err != nil { @@ -51,6 +96,27 @@ func (client *GlideClient) ConfigSet(parameters map[string]string) (string, erro return handleStringResponse(result) } +// Gets the values of configuration parameters. +// +// Note: Prior to Version 7.0.0, only one parameter can be send. +// +// See [valkey.io] for details. +// +// Parameters: +// +// args - A slice of configuration parameter names to retrieve values for. +// +// Return value: +// +// A map of api.Result[string] corresponding to the configuration parameters. +// +// For example: +// +// result, err := client.ConfigGet([]string{"timeout" , "maxmemory"}) +// // result["timeout"] = "1000" +// // result["maxmemory"] = "1GB" +// +// [valkey.io]: https://valkey.io/commands/config-get/ func (client *GlideClient) ConfigGet(args []string) (map[string]string, error) { res, err := client.executeCommand(C.ConfigGet, args) if err != nil { @@ -59,6 +125,22 @@ func (client *GlideClient) ConfigGet(args []string) (map[string]string, error) { return handleStringToStringMapResponse(res) } +// Select changes the currently selected database. +// +// Parameters: +// +// index - The index of the database to select. +// +// Return value: +// +// A simple `"OK"` response. +// +// Example: +// +// result, err := client.Select(2) +// result: "OK" +// +// [valkey.io]: https://valkey.io/commands/select/ func (client *GlideClient) Select(index int64) (string, error) { result, err := client.executeCommand(C.Select, []string{utils.IntToString(index)}) if err != nil { diff --git a/go/api/glide_cluster_client.go b/go/api/glide_cluster_client.go index cc672a91b5..c8a0216563 100644 --- a/go/api/glide_cluster_client.go +++ b/go/api/glide_cluster_client.go @@ -30,6 +30,33 @@ func NewGlideClusterClient(config *GlideClusterClientConfiguration) (GlideCluste return &glideClusterClient{client}, nil } +// CustomCommand executes a single command, specified by args, without checking inputs. Every part of the command, +// including the command name and subcommands, should be added as a separate value in args. The returning value depends on +// the executed +// command. +// +// The command will be routed automatically based on the passed command's default request policy. +// +// See [Valkey GLIDE Wiki] for details on the restrictions and limitations of the custom command API. +// +// This function should only be used for single-response commands. Commands that don't return complete response and awaits +// (such as SUBSCRIBE), or that return potentially more than a single response (such as XREAD), or that change the client's +// behavior (such as entering pub/sub mode on RESP2 connections) shouldn't be called using this function. +// +// Parameters: +// +// args - Arguments for the custom command including the command name. +// +// Return value: +// +// The returned value for the custom command. +// +// For example: +// +// result, err := client.CustomCommand([]string{"ping"}) +// result.Value().(string): "PONG" +// +// [Valkey GLIDE Wiki]: https://github.com/valkey-io/valkey-glide/wiki/General-Concepts#custom-command func (client *glideClusterClient) CustomCommand(args []string) (ClusterValue[interface{}], error) { res, err := client.executeCommand(C.CustomCommand, args) if err != nil { diff --git a/go/api/hyperloglog_commands.go b/go/api/hyperloglog_commands.go index ae6dfda91b..a09dcd7da2 100644 --- a/go/api/hyperloglog_commands.go +++ b/go/api/hyperloglog_commands.go @@ -8,7 +8,6 @@ package api // // [valkey.io]: https://valkey.io/commands/#hyperloglog type HyperLogLogCommands interface { - PfAdd(key string, elements []string) (int64, error) PfCount(keys []string) (int64, error) diff --git a/go/api/server_management_commands.go b/go/api/server_management_commands.go index 3653f17903..c0bc273380 100644 --- a/go/api/server_management_commands.go +++ b/go/api/server_management_commands.go @@ -8,57 +8,9 @@ package api // // [valkey.io]: https://valkey.io/commands/#server type ServerManagementCommands interface { - // Select changes the currently selected database. - // - // Parameters: - // index - The index of the database to select. - // - // Return value: - // A simple `"OK"` response. - // - // Example: - // result, err := client.Select(2) - // result: "OK" - // - // [valkey.io]: https://valkey.io/commands/select/ Select(index int64) (string, error) - // Gets the values of configuration parameters. - // - // Note: Prior to Version 7.0.0, only one parameter can be send. - // - // See [valkey.io] for details. - // - // Parameters: - // args - A slice of configuration parameter names to retrieve values for. - // - // Return value: - // A map of api.Result[string] corresponding to the configuration parameters. - // - // For example: - // result, err := client.ConfigGet([]string{"timeout" , "maxmemory"}) - // // result["timeout"] = "1000" - // // result["maxmemory"] = "1GB" - // - // [valkey.io]: https://valkey.io/commands/config-get/ ConfigGet(args []string) (map[string]string, error) - // Sets configuration parameters to the specified values. - // - // Note: Prior to Version 7.0.0, only one parameter can be send. - // - // See [valkey.io] for details. - // - // Parameters: - // parameters - A map consisting of configuration parameters and their respective values to set. - // - // Return value: - // `"OK"` if all configurations have been successfully set. Otherwise, raises an error. - // - // For example: - // result, err := client.ConfigSet(map[string]string{"timeout": "1000", "maxmemory": "1GB"}) - // result: "OK" - // - // [valkey.io]: https://valkey.io/commands/config-set/ ConfigSet(parameters map[string]string) (string, error) } diff --git a/go/api/stream_commands.go b/go/api/stream_commands.go index 49b2ab7385..0df5d48b10 100644 --- a/go/api/stream_commands.go +++ b/go/api/stream_commands.go @@ -10,7 +10,6 @@ import "github.com/valkey-io/valkey-glide/go/glide/api/options" // // [valkey.io]: https://valkey.io/commands/#stream type StreamCommands interface { - XAdd(key string, values [][]string) (Result[string], error) XAddWithOptions(key string, values [][]string, options *options.XAddOptions) (Result[string], error) diff --git a/go/api/string_commands.go b/go/api/string_commands.go index 72f4fe545a..1641742092 100644 --- a/go/api/string_commands.go +++ b/go/api/string_commands.go @@ -8,7 +8,6 @@ package api // // [valkey.io]: https://valkey.io/commands/#string type StringCommands interface { - Set(key string, value string) (string, error) SetWithOptions(key string, value string, options *SetOptions) (Result[string], error) From e14cc01d6f055136509121d827cdf051acf81387 Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Tue, 21 Jan 2025 11:54:34 -0800 Subject: [PATCH 19/21] Addressed some documentation changes in examples Signed-off-by: Edward Liang --- go/api/base_client.go | 133 +++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 32f4aa79aa..eb7f4096c7 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -663,13 +663,13 @@ func (client *baseClient) Strlen(key string) (int64, error) { // The length of the string stored at `key` after it was modified. // // For example: -// 1. result, err := client.SetRange("key", 6, "GLIDE"); +// 1. result, err := client.SetRange("key", 6, "GLIDE") // result: 11 (New key created with length of 11 bytes) // value, err := client.Get("key") // value.Value(): "\x00\x00\x00\x00\x00\x00GLIDE" // 2. "key": "愛" (value char takes 3 bytes) -// result, err = client.SetRange("key", 1, "a") -// result.Value(): �a� // (becomes an invalid UTF-8 string) +// result, err := client.SetRange("key", 1, "a") +// result: 3 // // [valkey.io]: https://valkey.io/commands/setrange/ func (client *baseClient) SetRange(key string, offset int, value string) (int64, error) { @@ -962,11 +962,9 @@ func (client *baseClient) HSet(key string, values map[string]string) (int64, err // For example: // // payload1, err := client.HSetNX("myHash", "field", "value") -// // payload1.Value(): true -// // payload1.IsNil(): false +// // payload1: true // payload2, err := client.HSetNX("myHash", "field", "newValue") -// // payload2.Value(): false -// // payload2.IsNil(): false +// // payload2: false // // [valkey.io]: https://valkey.io/commands/hsetnx/ func (client *baseClient) HSetNX(key string, field string, value string) (bool, error) { @@ -1081,11 +1079,9 @@ func (client *baseClient) HVals(key string) ([]string, error) { // For example: // // exists, err := client.HExists("my_hash", "field1") -// // exists.Value(): true -// // exists.IsNil(): false +// // exists: true // exists, err = client.HExists("my_hash", "non_existent_field") -// // exists.Value(): false -// // exists.IsNil(): false +// // exists: false // // [valkey.io]: https://valkey.io/commands/hexists/ func (client *baseClient) HExists(key string, field string) (bool, error) { @@ -1141,8 +1137,7 @@ func (client *baseClient) HKeys(key string) ([]string, error) { // For example: // // strlen, err := client.HStrLen("my_hash", "my_field") -// // strlen.Value(): 10 -// // strlen.IsNil(): false +// // strlen: 10 // // [valkey.io]: https://valkey.io/commands/hstrlen/ func (client *baseClient) HStrLen(key string, field string) (int64, error) { @@ -1473,12 +1468,12 @@ func (client *baseClient) LPop(key string) (Result[string], error) { // // Return value: // -// An array of the popped elements as Result[string] will be returned depending on the list's length +// An array of the popped elements as strings will be returned depending on the list's length // If key does not exist, nil will be returned. // // For example: // 1. result, err := client.LPopCount("my_list", 2) -// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")} +// result: []string{"value1", "value2"} // 2. result, err := client.LPopCount("non_existent") // result: nil // @@ -1825,10 +1820,10 @@ func (client *baseClient) SCard(key string) (int64, error) { // Example: // // result1, err := client.SIsMember("mySet", "member1") -// // result1.Value(): true +// // result1: true // // Indicates that "member1" exists in the set "mySet". // result2, err := client.SIsMember("mySet", "nonExistingMember") -// // result2.Value(): false +// // result2: false // // Indicates that "nonExistingMember" does not exist in the set "mySet". // // [valkey.io]: https://valkey.io/commands/sismember/ @@ -2122,11 +2117,11 @@ func (client *baseClient) SPop(key string) (Result[string], error) { // // client.SAdd("myKey", []string{"one", "two"}) // value1, err := client.SMIsMember("myKey", []string{"two", "three"}) -// // value1[0].Value(): true -// // value1[1].Value(): false +// // value1[0]: true +// // value1[1]: false // // err: nil -// value2, err := client.SPop("nonExistingKey", []string{"one"}) -// // value2[0].Value(): false +// value2, err := client.SMIsMember("nonExistingKey", []string{"one"}) +// // value2[0]: false // // err: nil // // [valkey.io]: https://valkey.io/commands/smismember/ @@ -2532,12 +2527,12 @@ func (client *baseClient) RPop(key string) (Result[string], error) { // // Return value: // -// An array of popped elements as Result[string] will be returned depending on the list's length. +// An array of popped elements as strings will be returned depending on the list's length. // If key does not exist, nil will be returned. // // For example: // 1. result, err := client.RPopCount("my_list", 2) -// result: []api.Result[string]{api.CreateStringResult("value1"), api.CreateStringResult("value2")} +// result: []string{"value1", "value2"} // 2. result, err := client.RPop("non_exiting_key") // result: nil // @@ -2849,8 +2844,7 @@ func (client *baseClient) LMPopCount( // // keys - An array of keys to lists. // listDirection - The direction based on which elements are popped from - see [api.ListDirection]. -// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block -// indefinitely. +// timeoutSecs - The number of seconds to wait for a blocking operation to complete. A value of 0 will block indefinitely. // // Return value: // @@ -3205,11 +3199,13 @@ func (client *baseClient) Del(keys []string) (int64, error) { // keys - One or more keys to check if they exist. // // Return value: -// Returns the number of existing keys. +// +// Returns the number of existing keys. // // Example: -// result, err := client.Exists([]string{"key1", "key2", "key3"}) -// result: 2 +// +// result, err := client.Exists([]string{"key1", "key2", "key3"}) +// result: 2 // // [valkey.io]: https://valkey.io/commands/exists/ func (client *baseClient) Exists(keys []string) (int64, error) { @@ -3221,11 +3217,11 @@ func (client *baseClient) Exists(keys []string) (int64, error) { return handleIntResponse(result) } -// Expire sets a timeout on key. After the timeout has expired, the key will automatically be deleted +// Expire sets a timeout on key. After the timeout has expired, the key will automatically be deleted. // // If key already has an existing expire set, the time to live is updated to the new value. // If seconds is a non-positive number, the key will be deleted rather than expired. -// The timeout will only be cleared by commands that delete or overwrite the contents of key +// The timeout will only be cleared by commands that delete or overwrite the contents of key. // // Parameters: // key - The key to expire. @@ -3453,10 +3449,9 @@ func (client *baseClient) PExpireAt(key string, unixTimestampInMilliSeconds int6 // Sets a timeout on key. It takes an absolute Unix timestamp (milliseconds since // January 1, 1970) instead of specifying the number of milliseconds. -// A timestamp in the past will delete the key immediately. After the timeout has -// expired, the key will automatically be deleted -// If key already has an existing expire set, the time to live is -// updated to the new value/ +// A timestamp in the past will delete the key immediately. After the timeout has expired, the key will automatically be +// deleted. +// If key already has an existing expire set, the time to live is updated to the new value. // The timeout will only be cleared by commands that delete or overwrite the contents of key // // Parameters: @@ -3736,7 +3731,7 @@ func (client *baseClient) Type(key string) (string, error) { // // Parameters: // -// The keys to update last access time. +// keys - The keys to update last access time. // // Return value: // @@ -3770,8 +3765,8 @@ func (client *baseClient) Touch(keys []string) (int64, error) { // // Parameters: // -// key to rename. -// newKey The new name of the key. +// key - The key to rename. +// newKey - The new name of the key. // // Return value: // If the key was successfully renamed, return "OK". If key does not exist, an error is thrown. @@ -3801,12 +3796,12 @@ func (client *baseClient) Rename(key string, newKey string) (string, error) { // // Parameters: // -// key to rename. -// newKey The new name of the key. +// key - The key to rename. +// newKey - The new name of the key. // // Return value: // -// `true` if k`ey was renamed to `newKey`, `false` if `newKey` already exists. +// `true` if key was renamed to `newKey`, `false` if `newKey` already exists. // // Example: // @@ -4158,8 +4153,11 @@ func (client *baseClient) ZAdd( // // Example: // -// res, err := client.ZAddWithOptions(key, map[string]float64{"one": 1.0, "two": 2.0, "three": 3.0}, -// options.NewZAddOptionsBuilder().SetChanged(true).Build()) +// res, err := client.ZAddWithOptions( +// key, +// map[string]float64{"one": 1.0, "two": 2.0, "three": 3.0}, +// options.NewZAddOptionsBuilder().SetChanged(true).Build() +// ) // fmt.Println(res) // Output: 3 // // [valkey.io]: https://valkey.io/commands/zadd/ @@ -4272,7 +4270,7 @@ func (client *baseClient) ZAddIncrWithOptions( // If key does not exist, a new sorted set with the specified member as its sole member // is created. // -// see [valkey.io] for details. +// See [valkey.io] for details. // // Parameters: // @@ -4843,15 +4841,15 @@ func (client *baseClient) ZRevRankWithScore(key string, member string) (Result[i // For example: // // xAddResult, err = client.XAddWithOptions( -// "key1", -// [][]string{{field1, "foo4"}, {field2, "bar4"}}, -// options.NewXAddOptions().SetTrimOptions( -// options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), -// ), -// ) -// xTrimResult, err := client.XTrim( -// "key1", -// options.NewXTrimOptionsWithMaxLen(1).SetExactTrimming(), +// "key1", +// [][]string{{field1, "foo4"}, {field2, "bar4"}}, +// options.NewXAddOptions().SetTrimOptions( +// options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), +// ), +// ) +// xTrimResult, err := client.XTrim( +// "key1", +// options.NewXTrimOptionsWithMaxLen(1).SetExactTrimming(), // ) // fmt.Println(xTrimResult) // Output: 1 // @@ -4882,14 +4880,14 @@ func (client *baseClient) XTrim(key string, options *options.XTrimOptions) (int6 // // For example: // -// xAddResult, err = client.XAddWithOptions( -// "key1", -// [][]string{{field1, "foo4"}, {field2, "bar4"}}, -// options.NewXAddOptions().SetTrimOptions( -// options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), -// ), -// ) -// xLenResult, err = client.XLen("key1") +// xAddResult, err = client.XAddWithOptions( +// "key1", +// [][]string{{field1, "foo4"}, {field2, "bar4"}}, +// options.NewXAddOptions().SetTrimOptions( +// options.NewXTrimOptionsWithMinId(id).SetExactTrimming(), +// ), +// ) +// xLenResult, err = client.XLen("key1") // fmt.Println(xLenResult) // Output: 2 // // [valkey.io]: https://valkey.io/commands/xlen/ @@ -5478,10 +5476,11 @@ func (client *baseClient) XGroupCreateWithOptions( // Return OK if successfully create a key with a value . // // Example: -// result, err := client.Restore("key",ttl, value) +// +// result, err := client.Restore("key",ttl, value) // // if err != nil { -// // handle error +// // handle error // } // fmt.Println(result.Value()) // Output: OK // @@ -5505,11 +5504,12 @@ func (client *baseClient) Restore(key string, ttl int64, value string) (Result[s // Return OK if successfully create a key with a value. // // Example: -// restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) -// resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) +// +// restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) +// resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) // // if err != nil { -// // handle error +// // handle error // } // fmt.Println(result.Value()) // Output: OK // @@ -5575,7 +5575,8 @@ func (client *baseClient) Dump(key string) (Result[string], error) { // key as a String. Otherwise, returns null. // // Example: -// result, err := client.ObjectEncoding("mykeyRenamenx") +// +// result, err := client.ObjectEncoding("mykeyRenamenx") // // if err != nil { // // handle error From f77e2e349cd8a7b860f394006f985619339666da Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Wed, 22 Jan 2025 13:41:20 -0800 Subject: [PATCH 20/21] Renamed interfaces to be more informative Signed-off-by: Edward Liang --- go/api/base_client.go | 2 +- go/api/glide_client.go | 10 +++++----- go/api/glide_cluster_client.go | 10 +++++----- go/integTest/glide_test_suite_test.go | 12 ++++++------ go/integTest/shared_commands_test.go | 22 +++++++++++----------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index eb7f4096c7..33eecc93e3 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -22,7 +22,7 @@ import ( "google.golang.org/protobuf/proto" ) -// BaseClient defines an interface for methods common to both [IGlideClient] and [IGlideClusterClient]. +// BaseClient defines an interface for methods common to both [GlideClientCommands] and [GlideClusterClientCommands]. type BaseClient interface { StringCommands HashCommands diff --git a/go/api/glide_client.go b/go/api/glide_client.go index 985cf9b070..60fe085cc7 100644 --- a/go/api/glide_client.go +++ b/go/api/glide_client.go @@ -11,10 +11,10 @@ import ( ) // GlideClient interface compliance check. -var _ IGlideClient = (*GlideClient)(nil) +var _ GlideClientCommands = (*GlideClient)(nil) -// IGlideClient is a client used for connection in Standalone mode. -type IGlideClient interface { +// GlideClientCommands is a client used for connection in Standalone mode. +type GlideClientCommands interface { BaseClient GenericCommands ServerManagementCommands @@ -25,8 +25,8 @@ type GlideClient struct { *baseClient } -// NewGlideClient creates a [IGlideClient] in standalone mode using the given [GlideClientConfiguration]. -func NewGlideClient(config *GlideClientConfiguration) (IGlideClient, error) { +// NewGlideClient creates a [GlideClientCommands] in standalone mode using the given [GlideClientConfiguration]. +func NewGlideClient(config *GlideClientConfiguration) (GlideClientCommands, error) { client, err := createClient(config) if err != nil { return nil, err diff --git a/go/api/glide_cluster_client.go b/go/api/glide_cluster_client.go index 7486487717..42d80db068 100644 --- a/go/api/glide_cluster_client.go +++ b/go/api/glide_cluster_client.go @@ -7,10 +7,10 @@ package api import "C" // GlideClusterClient interface compliance check. -var _ IGlideClusterClient = (*GlideClusterClient)(nil) +var _ GlideClusterClientCommands = (*GlideClusterClient)(nil) -// IGlideClusterClient is a client used for connection in cluster mode. -type IGlideClusterClient interface { +// GlideClusterClientCommands is a client used for connection in cluster mode. +type GlideClusterClientCommands interface { BaseClient GenericClusterCommands } @@ -20,8 +20,8 @@ type GlideClusterClient struct { *baseClient } -// NewGlideClusterClient creates a [IGlideClusterClient] in cluster mode using the given [GlideClusterClientConfiguration]. -func NewGlideClusterClient(config *GlideClusterClientConfiguration) (IGlideClusterClient, error) { +// NewGlideClusterClient creates a [GlideClusterClientCommands] in cluster mode using the given [GlideClusterClientConfiguration]. +func NewGlideClusterClient(config *GlideClusterClientConfiguration) (GlideClusterClientCommands, error) { client, err := createClient(config) if err != nil { return nil, err diff --git a/go/integTest/glide_test_suite_test.go b/go/integTest/glide_test_suite_test.go index cbce0ed3ef..10532b0520 100644 --- a/go/integTest/glide_test_suite_test.go +++ b/go/integTest/glide_test_suite_test.go @@ -24,8 +24,8 @@ type GlideTestSuite struct { clusterHosts []api.NodeAddress tls bool serverVersion string - clients []api.IGlideClient - clusterClients []api.IGlideClusterClient + clients []api.GlideClientCommands + clusterClients []api.GlideClusterClientCommands } var ( @@ -227,7 +227,7 @@ func (suite *GlideTestSuite) getDefaultClients() []api.BaseClient { return []api.BaseClient{suite.defaultClient(), suite.defaultClusterClient()} } -func (suite *GlideTestSuite) defaultClient() api.IGlideClient { +func (suite *GlideTestSuite) defaultClient() api.GlideClientCommands { config := api.NewGlideClientConfiguration(). WithAddress(&suite.standaloneHosts[0]). WithUseTLS(suite.tls). @@ -235,7 +235,7 @@ func (suite *GlideTestSuite) defaultClient() api.IGlideClient { return suite.client(config) } -func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) api.IGlideClient { +func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) api.GlideClientCommands { client, err := api.NewGlideClient(config) assert.Nil(suite.T(), err) @@ -245,7 +245,7 @@ func (suite *GlideTestSuite) client(config *api.GlideClientConfiguration) api.IG return client } -func (suite *GlideTestSuite) defaultClusterClient() api.IGlideClusterClient { +func (suite *GlideTestSuite) defaultClusterClient() api.GlideClusterClientCommands { config := api.NewGlideClusterClientConfiguration(). WithAddress(&suite.clusterHosts[0]). WithUseTLS(suite.tls). @@ -253,7 +253,7 @@ func (suite *GlideTestSuite) defaultClusterClient() api.IGlideClusterClient { return suite.clusterClient(config) } -func (suite *GlideTestSuite) clusterClient(config *api.GlideClusterClientConfiguration) api.IGlideClusterClient { +func (suite *GlideTestSuite) clusterClient(config *api.GlideClusterClientConfiguration) api.GlideClusterClientCommands { client, err := api.NewGlideClusterClient(config) assert.Nil(suite.T(), err) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 12ceecebd5..bfc63fc3b8 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4170,9 +4170,9 @@ func sendWithCustomCommand(suite *GlideTestSuite, client api.BaseClient, args [] var res any var err error switch c := client.(type) { - case api.IGlideClient: + case api.GlideClientCommands: res, err = c.CustomCommand(args) - case api.IGlideClusterClient: + case api.GlideClusterClientCommands: res, err = c.CustomCommand(args) default: suite.FailNow(errMsg) @@ -4468,7 +4468,7 @@ func (suite *GlideTestSuite) TestXRead() { // ensure that commands doesn't time out even if timeout > request timeout var testClient api.BaseClient - if _, ok := client.(api.IGlideClient); ok { + if _, ok := client.(api.GlideClientCommands); ok { testClient = suite.client(api.NewGlideClientConfiguration(). WithAddress(&suite.standaloneHosts[0]). WithUseTLS(suite.tls)) @@ -5572,7 +5572,7 @@ func (suite *GlideTestSuite) TestXPending() { // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be // collapsed once the native commands are added in a subsequent release. - execStandalone := func(client api.IGlideClient) { + execStandalone := func(client api.GlideClientCommands) { // 1. Arrange the data key := uuid.New().String() groupName := "group" + uuid.New().String() @@ -5646,7 +5646,7 @@ func (suite *GlideTestSuite) TestXPending() { assert.Equal(suite.T(), streamid_2.Value(), detailResult[1].Id) } - execCluster := func(client api.IGlideClusterClient) { + execCluster := func(client api.GlideClusterClientCommands) { // 1. Arrange the data key := uuid.New().String() groupName := "group" + uuid.New().String() @@ -5728,9 +5728,9 @@ func (suite *GlideTestSuite) TestXPending() { // this is only needed in order to be able to use custom commands. // Once the native commands are added, this logic will be refactored. switch c := client.(type) { - case api.IGlideClient: + case api.GlideClientCommands: execStandalone(c) - case api.IGlideClusterClient: + case api.GlideClusterClientCommands: execCluster(c) } }) @@ -5746,7 +5746,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { // each use of CustomCommand would make the tests difficult to read and maintain. These tests can be // collapsed once the native commands are added in a subsequent release. - execStandalone := func(client api.IGlideClient) { + execStandalone := func(client api.GlideClientCommands) { // 1. Arrange the data key := uuid.New().String() missingKey := uuid.New().String() @@ -5896,7 +5896,7 @@ func (suite *GlideTestSuite) TestXPendingFailures() { assert.True(suite.T(), strings.Contains(err.Error(), "WRONGTYPE")) } - execCluster := func(client api.IGlideClusterClient) { + execCluster := func(client api.GlideClusterClientCommands) { // 1. Arrange the data key := uuid.New().String() missingKey := uuid.New().String() @@ -6052,9 +6052,9 @@ func (suite *GlideTestSuite) TestXPendingFailures() { // this is only needed in order to be able to use custom commands. // Once the native commands are added, this logic will be refactored. switch c := client.(type) { - case api.IGlideClient: + case api.GlideClientCommands: execStandalone(c) - case api.IGlideClusterClient: + case api.GlideClusterClientCommands: execCluster(c) } }) From f27fcf33a084bf0714d5d71d6dea7069c4704c4e Mon Sep 17 00:00:00 2001 From: Edward Liang Date: Wed, 22 Jan 2025 13:56:02 -0800 Subject: [PATCH 21/21] Fix lint error Signed-off-by: Edward Liang --- go/api/glide_cluster_client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/api/glide_cluster_client.go b/go/api/glide_cluster_client.go index 42d80db068..2fba684d8c 100644 --- a/go/api/glide_cluster_client.go +++ b/go/api/glide_cluster_client.go @@ -20,7 +20,8 @@ type GlideClusterClient struct { *baseClient } -// NewGlideClusterClient creates a [GlideClusterClientCommands] in cluster mode using the given [GlideClusterClientConfiguration]. +// NewGlideClusterClient creates a [GlideClusterClientCommands] in cluster mode using the given +// [GlideClusterClientConfiguration]. func NewGlideClusterClient(config *GlideClusterClientConfiguration) (GlideClusterClientCommands, error) { client, err := createClient(config) if err != nil {