From 2d122ef9a1f0b3e848ea20137a3d275e82e289c2 Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Sun, 29 Dec 2024 18:56:48 +0000 Subject: [PATCH 1/5] Sort,Sort_RO,Sort Store commands Signed-off-by: Niharika Bhavaraju --- go/api/base_client.go | 51 +++++ go/api/command_options.go | 123 ++++++++++++ go/api/generic_commands.go | 172 ++++++++++++++++ go/integTest/shared_commands_test.go | 217 ++++++++++++++++++++ go/integTest/standalone_commands_test.go | 242 +++++++++++++++++++++++ 5 files changed, 805 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index 3de2a5170f..2f553cf4ab 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1405,3 +1405,54 @@ func (client *baseClient) ZPopMaxWithCount(key string, count int64) (map[Result[ } return handleStringDoubleMapResponse(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 *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 *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 handleLongResponse(result) +} + +func (client *baseClient) SortStoreWithOptions(key string, destination string, 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 handleLongResponse(result) +} diff --git a/go/api/command_options.go b/go/api/command_options.go index d2934b869e..26fa417587 100644 --- a/go/api/command_options.go +++ b/go/api/command_options.go @@ -3,6 +3,7 @@ package api import ( + "fmt" "strconv" "github.com/valkey-io/valkey-glide/go/glide/utils" @@ -279,6 +280,128 @@ 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 04fd69d520..b75b0776c3 100644 --- a/go/api/generic_commands.go +++ b/go/api/generic_commands.go @@ -314,6 +314,178 @@ type GenericBaseCommands interface { // [valkey.io]: https://valkey.io/commands/pttl/ PTTL(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 {@link #sortStore(string, string)}. + // + // 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 {@link #sortStore(string, string)}. + // + // 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. + // 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. + // sortOptions- The {@link SortOptions}. + // + // 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 *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 {@link #sort(String)} or {@link #sortReadOnly(String)}. + // + // 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: + // 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 {@link #sort(String)} or {@link #sortReadOnly(String)}. + // + // 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. + // 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. + // destination - The key where the sorted result will be stored. + // sortOptions- The {@link SortOptions}. + // + // 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 *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 {@link 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 {@link ReadFrom} strategy. + // + // 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. + // 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. + // sortOptions- The {@link SortOptions}. + // + // 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 *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/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index b508b69004..59a15f75cd 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3623,6 +3623,223 @@ func (suite *GlideTestSuite) TestPTTL_WithExpiredKey() { }) } +func (suite *GlideTestSuite) TestSortWithOptions_AscendingOrder() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"b", "a", "c"}) + + options := api.NewSortOptions(). + SetOrderBy(api.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 := api.NewSortOptions(). + SetOrderBy(api.DESC). + SetIsAlpha(true). + SetLimit(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 := api.NewSortOptions().SetOrderBy(api.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 := api.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 := api.NewSortOptions().SetLimit(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.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.runWithDefaultClients(func(client api.BaseClient) { + key := uuid.New().String() + client.LPush(key, []string{"b", "a", "c"}) + + options := api.NewSortOptions(). + SetOrderBy(api.DESC). + SetIsAlpha(true). + SetLimit(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") diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 318c2d18ed..60e3b853de 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -225,3 +225,245 @@ func (suite *GlideTestSuite) TestConfigSetAndGet_invalidArgs() { assert.Equal(suite.T(), map[api.Result[string]]api.Result[string]{}, result2) assert.Nil(suite.T(), err) } + +func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { + client := suite.defaultClient() + 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 := api.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(api.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() { + client := suite.defaultClient() + 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 := api.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(api.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() { + client := suite.defaultClient() + 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 := api.NewSortOptions(). + SetOrderBy(api.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() { + client := suite.defaultClient() + 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 := api.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) +} + +func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { + client := suite.defaultClient() + 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 := api.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(api.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() + 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 := api.NewSortOptions(). + SetByPattern("weight_*"). + SetOrderBy(api.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() + 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 := api.NewSortOptions(). + SetOrderBy(api.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 ca3c534417401604371ab401b9bf4fcb3f6def5e Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Sun, 29 Dec 2024 19:36:25 +0000 Subject: [PATCH 2/5] Fixed version issue for tests Signed-off-by: Niharika Bhavaraju --- go/integTest/shared_commands_test.go | 2 ++ go/integTest/standalone_commands_test.go | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index 59a15f75cd..c40639e0a0 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3799,6 +3799,7 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_Limit() { } 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"}) @@ -3818,6 +3819,7 @@ func (suite *GlideTestSuite) TestSortReadOnly_SuccessfulSort() { } 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"}) diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 60e3b853de..46e620227e 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -365,6 +365,9 @@ func (suite *GlideTestSuite) TestSortStoreWithOptions_ByPattern() { 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"}) @@ -390,6 +393,9 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { 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"}) @@ -418,6 +424,9 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_GetPatterns() { 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"}) From eaf901bb8dec161508ac598c7f77ab80a3ff658b Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Mon, 6 Jan 2025 11:46:25 +0000 Subject: [PATCH 3/5] Fixed review comments Signed-off-by: Niharika Bhavaraju --- go/api/base_client.go | 6 +- go/api/command_options.go | 123 --------------------- go/api/generic_commands.go | 22 ++-- go/api/options/sort_options.go | 129 +++++++++++++++++++++++ go/integTest/shared_commands_test.go | 12 +-- go/integTest/standalone_commands_test.go | 15 +-- 6 files changed, 157 insertions(+), 150 deletions(-) create mode 100644 go/api/options/sort_options.go 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_*"). From e218093c93bb226dc494b85d5ee24aea05418fa5 Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Wed, 8 Jan 2025 11:41:41 +0000 Subject: [PATCH 4/5] Fixed code review comments Signed-off-by: Niharika Bhavaraju --- go/api/generic_commands.go | 32 +++++++------- go/api/options/sort_options.go | 56 +++++++++++------------- go/integTest/shared_commands_test.go | 8 ++-- go/integTest/standalone_commands_test.go | 12 ++--- 4 files changed, 52 insertions(+), 56 deletions(-) diff --git a/go/api/generic_commands.go b/go/api/generic_commands.go index 8de028d17c..0e490954ba 100644 --- a/go/api/generic_commands.go +++ b/go/api/generic_commands.go @@ -13,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 `key` and `destination` map to different hash slots, the command + // In cluster mode, if `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 @@ -39,7 +39,7 @@ type GenericBaseCommands interface { // Exists returns the number of keys that exist in the database // // Note: - // In cluster mode, if `key` and `destination` map to different hash slots, the command + // In cluster mode, if `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 @@ -317,7 +317,7 @@ type GenericBaseCommands interface { // 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 {@link #sortStore(string, string)}. + // 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. @@ -337,21 +337,21 @@ type GenericBaseCommands interface { // 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 {@link #sortStore(string, string)}. + // To store the result into a new key, see the sortStore function. // // Note: - // In cluster mode, if `key` and `destination` map to different hash slots, the command + // 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 {@link SortOptions#byPattern} and {@link SortOptions#getPatterns} in cluster mode is + // 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 {@link SortOptions}. + // sortOptions - The SortOptions type. // // Return value: // An Array of sorted elements. @@ -371,7 +371,7 @@ type GenericBaseCommands interface { // 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 {@link #sort(String)} or {@link #sortReadOnly(String)}. + // 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 @@ -402,7 +402,7 @@ type GenericBaseCommands interface { // 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 {@link #sort(String)} or {@link #sortReadOnly(String)}. + // 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 @@ -411,13 +411,13 @@ type GenericBaseCommands interface { // 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 {@link SortOptions#byPattern} and {@link SortOptions#getPatterns} + // 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 {@link SortOptions}. + // sortOptions - The SortOptions type. // // Return value: // The number of elements in the sorted key stored at destination. @@ -435,7 +435,7 @@ type GenericBaseCommands interface { // 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 {@link ReadFrom} strategy. + // 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. @@ -455,21 +455,21 @@ type GenericBaseCommands interface { // 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 {@link ReadFrom} strategy. + // This command is routed depending on the client's ReadFrom strategy. // // Note: - // In cluster mode, if `key` and `destination` map to different hash slots, the command + // 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 {@link SortOptions#byPattern} and {@link SortOptions#getPatterns} in cluster mode is + // 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 {@link SortOptions}. + // sortOptions - The SortOptions type. // // Return value: // An Array of sorted elements. diff --git a/go/api/options/sort_options.go b/go/api/options/sort_options.go index a85726e30a..2cf90c69b9 100644 --- a/go/api/options/sort_options.go +++ b/go/api/options/sort_options.go @@ -38,36 +38,11 @@ const ( // 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 + Limit *Limit + OrderBy OrderBy + IsAlpha bool + ByPattern string + GetPatterns []string } func NewSortOptions() *SortOptions { @@ -77,26 +52,47 @@ func NewSortOptions() *SortOptions { } } +// Limit Limits the range of elements func (opts *SortOptions) SetLimit(offset, count int64) *SortOptions { opts.Limit = &Limit{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 diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index d0bd41adae..8ca40bd5f0 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -3737,7 +3737,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_AscendingOrder() { client.LPush(key, []string{"b", "a", "c"}) options := options.NewSortOptions(). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(true) sortResult, err := client.SortWithOptions(key, options) @@ -3759,7 +3759,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_DescendingOrder() { client.LPush(key, []string{"b", "a", "c"}) options := options.NewSortOptions(). - SetOrderBy(api.DESC). + SetOrderBy(options.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 := options.NewSortOptions().SetOrderBy(api.DESC).SetIsAlpha(false) + options := options.NewSortOptions().SetOrderBy(options.DESC).SetIsAlpha(false) result, err := client.SortStoreWithOptions(key, sortedKey, options) assert.Nil(suite.T(), err) @@ -3933,7 +3933,7 @@ func (suite *GlideTestSuite) TestSortReadyOnlyWithOptions_DescendingOrder() { client.LPush(key, []string{"b", "a", "c"}) options := options.NewSortOptions(). - SetOrderBy(api.DESC). + SetOrderBy(options.DESC). SetIsAlpha(true). SetLimit(0, 3) diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 940df7be5e..16885faf36 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -295,7 +295,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { options := options.NewSortOptions(). SetByPattern("weight_*"). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false) sortResult, err := client.SortWithOptions(key, options) @@ -321,7 +321,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_GetPatterns() { options := options.NewSortOptions(). SetByPattern("weight_*"). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false). AddGetPattern("object_*") @@ -352,7 +352,7 @@ func (suite *GlideTestSuite) TestSortWithOptions_SuccessfulSortByWeightAndGet() client.Set("object_item3", "Object 3") options := options.NewSortOptions(). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false). SetByPattern("weight_*"). AddGetPattern("object_*"). @@ -435,7 +435,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { options := options.NewSortOptions(). SetByPattern("weight_*"). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false) sortResult, err := client.SortReadOnlyWithOptions(key, options) @@ -463,7 +463,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_GetPatterns() { options := options.NewSortOptions(). SetByPattern("weight_*"). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false). AddGetPattern("object_*") @@ -497,7 +497,7 @@ func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_SuccessfulSortByWeightA client.Set("object_item3", "Object 3") options := options.NewSortOptions(). - SetOrderBy(api.ASC). + SetOrderBy(options.ASC). SetIsAlpha(false). SetByPattern("weight_*"). AddGetPattern("object_*"). From faa9306400db345e2f6bffafd79332bb283f9651 Mon Sep 17 00:00:00 2001 From: Niharika Bhavaraju Date: Thu, 9 Jan 2025 05:22:54 +0000 Subject: [PATCH 5/5] Fixed code review comments Signed-off-by: Niharika Bhavaraju --- go/api/base_client.go | 6 +- go/api/options/sort_options.go | 7 +- go/integTest/shared_commands_test.go | 265 +++++++++++++++++++++++ go/integTest/standalone_commands_test.go | 252 --------------------- 4 files changed, 274 insertions(+), 256 deletions(-) diff --git a/go/api/base_client.go b/go/api/base_client.go index 88a9b98d00..2088d042fd 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1526,7 +1526,11 @@ func (client *baseClient) SortStore(key string, destination string) (Result[int6 return handleLongResponse(result) } -func (client *baseClient) SortStoreWithOptions(key string, destination string, options *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/options/sort_options.go b/go/api/options/sort_options.go index 2cf90c69b9..5d010712ea 100644 --- a/go/api/options/sort_options.go +++ b/go/api/options/sort_options.go @@ -22,10 +22,8 @@ const ( // 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 + Count int64 } // OrderBy specifies the order to sort the elements. Can be ASC (ascending) or DESC(descending). @@ -53,6 +51,9 @@ func NewSortOptions() *SortOptions { } // Limit 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) SetLimit(offset, count int64) *SortOptions { opts.Limit = &Limit{Offset: offset, Count: count} return opts diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index ef4e34f077..59c8dd4a03 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -4703,3 +4703,268 @@ func (suite *GlideTestSuite) TestPersist() { assert.False(t, resultInvalidKey.Value()) }) } + +func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { + suite.SkipIfServerVersionLowerThanBy("8.0.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.0.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.0.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.0.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) + }) +} + +func (suite *GlideTestSuite) TestSortReadOnlyWithOptions_ExternalWeights() { + suite.SkipIfServerVersionLowerThanBy("8.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + 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() { + suite.SkipIfServerVersionLowerThanBy("8.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + 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() { + suite.SkipIfServerVersionLowerThanBy("8.0.0") + suite.runWithDefaultClients(func(client api.BaseClient) { + 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()) + }) +} diff --git a/go/integTest/standalone_commands_test.go b/go/integTest/standalone_commands_test.go index 16885faf36..26cd0d5077 100644 --- a/go/integTest/standalone_commands_test.go +++ b/go/integTest/standalone_commands_test.go @@ -8,7 +8,6 @@ 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" ) @@ -283,254 +282,3 @@ func (suite *GlideTestSuite) TestSelect_SwitchBetweenDatabases() { assert.Nil(suite.T(), err) assert.Equal(suite.T(), value2, result.Value()) } - -func (suite *GlideTestSuite) TestSortWithOptions_ExternalWeights() { - client := suite.defaultClient() - 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() { - client := suite.defaultClient() - 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() { - client := suite.defaultClient() - 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() { - client := suite.defaultClient() - 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) -} - -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()) -}