diff --git a/go/api/base_client.go b/go/api/base_client.go index a55d133aa8..a584328794 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1450,7 +1450,7 @@ func (client *baseClient) Sort(key string) ([]Result[string], error) { return handleStringArrayResponse(result) } -func (client *baseClient) SortWithOptions(key string, options *SortOptions) ([]Result[string], error) { +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 { @@ -1467,7 +1467,7 @@ func (client *baseClient) SortReadOnly(key string) ([]Result[string], error) { return handleStringArrayResponse(result) } -func (client *baseClient) SortReadOnlyWithOptions(key string, options *SortOptions) ([]Result[string], error) { +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 { @@ -1484,7 +1484,7 @@ func (client *baseClient) SortStore(key string, destination string) (Result[int6 return handleLongResponse(result) } -func (client *baseClient) SortStoreWithOptions(key string, destination string, options *SortOptions) (Result[int64], error) { +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 { diff --git a/go/api/command_options.go b/go/api/command_options.go index 26fa417587..d2934b869e 100644 --- a/go/api/command_options.go +++ b/go/api/command_options.go @@ -3,7 +3,6 @@ package api import ( - "fmt" "strconv" "github.com/valkey-io/valkey-glide/go/glide/utils" @@ -280,128 +279,6 @@ func (listDirection ListDirection) toString() (string, error) { } } -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" -) - -// Limit 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 Limit struct { - // The starting position of the range, zero based. - Offset int64 - // The maximum number of elements to include in the range. A negative count returns all elementsnfrom the offset. - 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 { - // Limit Limits the range of elements - Limit *Limit - - // OrderBy sets the order to sort by (ASC or DESC) - OrderBy OrderBy - - // IsAlpha determines whether to sort lexicographically (true) or numerically (false) - IsAlpha bool - - // 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. - ByPattern string - - // 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. - GetPatterns []string // List of patterns to retrieve external keys' values -} - -func NewSortOptions() *SortOptions { - return &SortOptions{ - OrderBy: ASC, // Default order is ascending - IsAlpha: false, // Default is numeric sorting - } -} - -func (opts *SortOptions) SetLimit(offset, count int64) *SortOptions { - opts.Limit = &Limit{Offset: offset, Count: count} - return opts -} - -func (opts *SortOptions) SetOrderBy(order OrderBy) *SortOptions { - opts.OrderBy = order - return opts -} - -func (opts *SortOptions) SetIsAlpha(isAlpha bool) *SortOptions { - opts.IsAlpha = isAlpha - return opts -} - -func (opts *SortOptions) SetByPattern(byPattern string) *SortOptions { - opts.ByPattern = byPattern - return opts -} - -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.Limit != nil { - args = append(args, LIMIT_COMMAND_STRING, fmt.Sprintf("%d", opts.Limit.Offset), fmt.Sprintf("%d", opts.Limit.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 -} - // This base option struct represents the common set of optional arguments for the SCAN family of commands. // Concrete implementations of this class are tied to specific SCAN commands (`SCAN`, `SSCAN`). type BaseScanOptions struct { diff --git a/go/api/generic_commands.go b/go/api/generic_commands.go index 87cb6c071d..8de028d17c 100644 --- a/go/api/generic_commands.go +++ b/go/api/generic_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" group of commands for standalone and cluster clients. // // See [valkey.io] for details. @@ -11,7 +13,7 @@ type GenericBaseCommands interface { // Del removes the specified keys from the database. A key is ignored if it does not exist. // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -37,7 +39,7 @@ type GenericBaseCommands interface { // Exists returns the number of keys that exist in the database // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -338,7 +340,7 @@ type GenericBaseCommands interface { // To store the result into a new key, see {@link #sortStore(string, string)}. // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -362,7 +364,7 @@ type GenericBaseCommands interface { // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/sort/ - SortWithOptions(key string, sortOptions *SortOptions) ([]Result[string], error) + 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 @@ -372,7 +374,7 @@ type GenericBaseCommands interface { // To get the sort result without storing it into a key, see {@link #sort(String)} or {@link #sortReadOnly(String)}. // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -403,7 +405,7 @@ type GenericBaseCommands interface { // To get the sort result without storing it into a key, see {@link #sort(String)} or {@link #sortReadOnly(String)}. // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -428,7 +430,7 @@ type GenericBaseCommands interface { // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/sort/ - SortStoreWithOptions(key string, destination string, sortOptions *SortOptions) (Result[int64], error) + 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 @@ -456,7 +458,7 @@ type GenericBaseCommands interface { // This command is routed depending on the client's {@link ReadFrom} strategy. // // Note: - // In cluster mode, if keys in `keyValueMap` map to different hash slots, the command + // 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 @@ -464,8 +466,6 @@ type GenericBaseCommands interface { // the request into sub-requests per slot to ensure atomicity. // The use of {@link SortOptions#byPattern} and {@link SortOptions#getPatterns} in cluster mode is // supported since Valkey version 8.0. - // The use of {@link SortOptions#byPattern} and {@link 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. @@ -482,7 +482,7 @@ type GenericBaseCommands interface { // result.IsNil(): false // // [valkey.io]: https://valkey.io/commands/sort/ - SortReadOnlyWithOptions(key string, sortOptions *SortOptions) ([]Result[string], error) + SortReadOnlyWithOptions(key string, sortOptions *options.SortOptions) ([]Result[string], error) // Unlink (delete) multiple keys from the database. A key is ignored if it does not exist. // This command, similar to Del However, this command does not block the server diff --git a/go/api/options/sort_options.go b/go/api/options/sort_options.go new file mode 100644 index 0000000000..a85726e30a --- /dev/null +++ b/go/api/options/sort_options.go @@ -0,0 +1,129 @@ +// 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" +) + +// Limit 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 Limit struct { + // The starting position of the range, zero based. + Offset int64 + // The maximum number of elements to include in the range. A negative count returns all elementsnfrom the offset. + 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 { + // Limit Limits the range of elements + Limit *Limit + + // OrderBy sets the order to sort by (ASC or DESC) + OrderBy OrderBy + + // IsAlpha determines whether to sort lexicographically (true) or numerically (false) + IsAlpha bool + + // 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. + ByPattern string + + // 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. + GetPatterns []string // List of patterns to retrieve external keys' values +} + +func NewSortOptions() *SortOptions { + return &SortOptions{ + OrderBy: ASC, // Default order is ascending + IsAlpha: false, // Default is numeric sorting + } +} + +func (opts *SortOptions) SetLimit(offset, count int64) *SortOptions { + opts.Limit = &Limit{Offset: offset, Count: count} + return opts +} + +func (opts *SortOptions) SetOrderBy(order OrderBy) *SortOptions { + opts.OrderBy = order + return opts +} + +func (opts *SortOptions) SetIsAlpha(isAlpha bool) *SortOptions { + opts.IsAlpha = isAlpha + return opts +} + +func (opts *SortOptions) SetByPattern(byPattern string) *SortOptions { + opts.ByPattern = byPattern + return opts +} + +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.Limit != nil { + args = append(args, LIMIT_COMMAND_STRING, utils.IntToString(opts.Limit.Offset), utils.IntToString(opts.Limit.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 f8c0fc5cc8..d0bd41adae 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3736,7 +3736,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_AscendingOrder() { key := uuid.New().String() client.LPush(key, []string{"b", "a", "c"}) - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetOrderBy(api.ASC). SetIsAlpha(true) @@ -3758,7 +3758,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_DescendingOrder() { key := uuid.New().String() client.LPush(key, []string{"b", "a", "c"}) - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetOrderBy(api.DESC). SetIsAlpha(true). SetLimit(0, 3) @@ -3836,7 +3836,7 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_DescendingOrder() { sortedKey := "{key}" + uuid.New().String() client.LPush(key, []string{"30", "20", "10", "40", "50"}) - options := api.NewSortOptions().SetOrderBy(api.DESC).SetIsAlpha(false) + options := options.NewSortOptions().SetOrderBy(api.DESC).SetIsAlpha(false) result, err := client.SortStoreWithOptions(key, sortedKey, options) assert.Nil(suite.T(), err) @@ -3862,7 +3862,7 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_AlphaSorting() { sortedKey := "{listKey}" + uuid.New().String() client.LPush(key, []string{"apple", "banana", "cherry", "date", "elderberry"}) - options := api.NewSortOptions().SetIsAlpha(true) + options := options.NewSortOptions().SetIsAlpha(true) result, err := client.SortStoreWithOptions(key, sortedKey, options) assert.Nil(suite.T(), err) @@ -3888,7 +3888,7 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_Limit() { sortedKey := "{listKey}" + uuid.New().String() client.LPush(key, []string{"10", "20", "30", "40", "50"}) - options := api.NewSortOptions().SetLimit(1, 3) + options := options.NewSortOptions().SetLimit(1, 3) result, err := client.SortStoreWithOptions(key, sortedKey, options) assert.Nil(suite.T(), err) @@ -3932,7 +3932,7 @@ func (suite *GlideTestSuite) TestSortReadyOnlyWithOptions_DescendingOrder() { key := uuid.New().String() client.LPush(key, []string{"b", "a", "c"}) - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetOrderBy(api.DESC). SetIsAlpha(true). SetLimit(0, 3) diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index bea23d870a..940df7be5e 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" ) @@ -292,7 +293,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { client.Set("weight_item2", "1") client.Set("weight_item3", "2") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetByPattern("weight_*"). SetOrderBy(api.ASC). SetIsAlpha(false) @@ -318,7 +319,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_GetPatterns() { client.Set("object_item2", "Object_2") client.Set("object_item3", "Object_3") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetByPattern("weight_*"). SetOrderBy(api.ASC). SetIsAlpha(false). @@ -350,7 +351,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_SuccessfulSortByWeightAndGet() client.Set("object_item2", "Object 2") client.Set("object_item3", "Object 3") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetOrderBy(api.ASC). SetIsAlpha(false). SetByPattern("weight_*"). @@ -400,7 +401,7 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_ByPattern() { client.Set("{listKey}weight_d", "1") client.Set("{listKey}weight_e", "4") - options := api.NewSortOptions().SetByPattern("{listKey}weight_*") + options := options.NewSortOptions().SetByPattern("{listKey}weight_*") result, err := client.SortStoreWithOptions(key, sortedKey, options) @@ -432,7 +433,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { client.Set("weight_item2", "1") client.Set("weight_item3", "2") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetByPattern("weight_*"). SetOrderBy(api.ASC). SetIsAlpha(false) @@ -460,7 +461,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_GetPatterns() { client.Set("object_item2", "Object_2") client.Set("object_item3", "Object_3") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetByPattern("weight_*"). SetOrderBy(api.ASC). SetIsAlpha(false). @@ -495,7 +496,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_SuccessfulSortByWeightA client.Set("object_item2", "Object 2") client.Set("object_item3", "Object 3") - options := api.NewSortOptions(). + options := options.NewSortOptions(). SetOrderBy(api.ASC). SetIsAlpha(false). SetByPattern("weight_*").