From b1a2c1f5860e44232a7a10e39058347e3fd04ac7 Mon Sep 17 00:00:00 2001 From: Edric Cuartero Date: Fri, 17 Jan 2025 11:39:44 +0800 Subject: [PATCH] GO Implement Dump, Restore and ObjectEncoding Command (#2781) * GO Implement Dump and ObjectEncoding command Signed-off-by: EdricCua --- go/api/base_client.go | 37 +++++++++ go/api/command_options.go | 77 +++++++++++++++++++ go/api/generic_base_commands.go | 85 +++++++++++++++++++++ go/integTest/shared_commands_test.go | 108 +++++++++++++++++++++++++++ 4 files changed, 307 insertions(+) diff --git a/go/api/base_client.go b/go/api/base_client.go index 79f8518623..00b09f1116 100644 --- a/go/api/base_client.go +++ b/go/api/base_client.go @@ -1995,3 +1995,40 @@ func (client *baseClient) XPendingWithOptions( } return handleXPendingDetailResponse(result) } + +func (client *baseClient) Restore(key string, ttl int64, value string) (Result[string], error) { + return client.RestoreWithOptions(key, ttl, value, NewRestoreOptionsBuilder()) +} + +func (client *baseClient) RestoreWithOptions(key string, ttl int64, + value string, options *RestoreOptions, +) (Result[string], error) { + optionArgs, err := options.toArgs() + if err != nil { + return CreateNilStringResult(), err + } + result, err := client.executeCommand(C.Restore, append([]string{ + key, + utils.IntToString(ttl), value, + }, optionArgs...)) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} + +func (client *baseClient) Dump(key string) (Result[string], error) { + result, err := client.executeCommand(C.Dump, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} + +func (client *baseClient) ObjectEncoding(key string) (Result[string], error) { + result, err := client.executeCommand(C.ObjectEncoding, []string{key}) + if err != nil { + return CreateNilStringResult(), err + } + return handleStringOrNilResponse(result) +} diff --git a/go/api/command_options.go b/go/api/command_options.go index f77902ca6c..dcf17446bc 100644 --- a/go/api/command_options.go +++ b/go/api/command_options.go @@ -278,3 +278,80 @@ func (listDirection ListDirection) toString() (string, error) { return "", &RequestError{"Invalid list direction"} } } + +// Optional arguments to Restore(key string, ttl int64, value string, option *RestoreOptions) +// +// Note IDLETIME and FREQ modifiers cannot be set at the same time. +// +// [valkey.io]: https://valkey.io/commands/restore/ +type RestoreOptions struct { + // Subcommand string to replace existing key. + replace string + // Subcommand string to represent absolute timestamp (in milliseconds) for TTL. + absTTL string + // It represents the idletime/frequency of object. + eviction Eviction +} + +func NewRestoreOptionsBuilder() *RestoreOptions { + return &RestoreOptions{} +} + +const ( + // Subcommand string to replace existing key. + Replace_keyword = "REPLACE" + + // Subcommand string to represent absolute timestamp (in milliseconds) for TTL. + ABSTTL_keyword string = "ABSTTL" +) + +// Custom setter methods to replace existing key. +func (restoreOption *RestoreOptions) SetReplace() *RestoreOptions { + restoreOption.replace = Replace_keyword + return restoreOption +} + +// Custom setter methods to represent absolute timestamp (in milliseconds) for TTL. +func (restoreOption *RestoreOptions) SetABSTTL() *RestoreOptions { + restoreOption.absTTL = ABSTTL_keyword + return restoreOption +} + +// For eviction purpose, you may use IDLETIME or FREQ modifiers. +type Eviction struct { + // It represent IDLETIME or FREQ. + Type EvictionType + // It represents count(int) of the idletime/frequency of object. + Count int64 +} + +type EvictionType string + +const ( + // It represents the idletime of object + IDLETIME EvictionType = "IDLETIME" + // It represents the frequency of object + FREQ EvictionType = "FREQ" +) + +// Custom setter methods set the idletime/frequency of object. +func (restoreOption *RestoreOptions) SetEviction(evictionType EvictionType, count int64) *RestoreOptions { + restoreOption.eviction.Type = evictionType + restoreOption.eviction.Count = count + return restoreOption +} + +func (opts *RestoreOptions) toArgs() ([]string, error) { + args := []string{} + var err error + if opts.replace != "" { + args = append(args, string(opts.replace)) + } + if opts.absTTL != "" { + args = append(args, string(opts.absTTL)) + } + if (opts.eviction != Eviction{}) { + args = append(args, string(opts.eviction.Type), utils.IntToString(opts.eviction.Count)) + } + return args, err +} diff --git a/go/api/generic_base_commands.go b/go/api/generic_base_commands.go index 1dab355a18..1f15eddd23 100644 --- a/go/api/generic_base_commands.go +++ b/go/api/generic_base_commands.go @@ -442,4 +442,89 @@ type GenericBaseCommands interface { // // [valkey.io]: https://valkey.io/commands/persist/ Persist(key string) (bool, error) + + // Create a key associated with a value that is obtained by + // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). + // + // Parameters: + // key - The key to create. + // ttl - The expiry time (in milliseconds). If 0, the key will persist. + // value - The serialized value to deserialize and assign to key. + // + // Return value: + // Return OK if successfully create a key with a value . + // + // Example: + // result, err := client.Restore("key",ttl, value) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: OK + // + // [valkey.io]: https://valkey.io/commands/restore/ + Restore(key string, ttl int64, value string) (Result[string], error) + + // Create a key associated with a value that is obtained by + // deserializing the provided serialized value (obtained via [valkey.io]: Https://valkey.io/commands/dump/). + // + // Parameters: + // key - The key to create. + // ttl - The expiry time (in milliseconds). If 0, the key will persist. + // value - The serialized value to deserialize and assign to key. + // restoreOptions - Set restore options with replace and absolute TTL modifiers, object idletime and frequency + // + // Return value: + // Return OK if successfully create a key with a value. + // + // Example: + // restoreOptions := api.NewRestoreOptionsBuilder().SetReplace().SetABSTTL().SetEviction(api.FREQ, 10) + // resultRestoreOpt, err := client.RestoreWithOptions(key, ttl, value, restoreOptions) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: OK + // + // [valkey.io]: https://valkey.io/commands/restore/ + RestoreWithOptions(key string, ttl int64, value string, option *RestoreOptions) (Result[string], error) + + // Returns the internal encoding for the Valkey object stored at key. + // + // Note: + // When in cluster mode, both key and newkey must map to the same hash slot. + // + // Parameters: + // The key of the object to get the internal encoding of. + // + // Return value: + // If key exists, returns the internal encoding of the object stored at + // key as a String. Otherwise, returns null. + // + // Example: + // result, err := client.ObjectEncoding("mykeyRenamenx") + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: embstr + // + // [valkey.io]: https://valkey.io/commands/object-encoding/ + ObjectEncoding(key string) (Result[string], error) + + // Serialize the value stored at key in a Valkey-specific format and return it to the user. + // + // Parameters: + // The key to serialize. + // + // Return value: + // The serialized value of the data stored at key + // If key does not exist, null will be returned. + // + // Example: + // result, err := client.Dump([]string{"key"}) + // if err != nil { + // // handle error + // } + // fmt.Println(result.Value()) // Output: (Serialized Value) + // + // [valkey.io]: https://valkey.io/commands/dump/ + Dump(key string) (Result[string], error) } diff --git a/go/integTest/shared_commands_test.go b/go/integTest/shared_commands_test.go index be729bdad4..5a01b8595b 100644 --- a/go/integTest/shared_commands_test.go +++ b/go/integTest/shared_commands_test.go @@ -5716,3 +5716,111 @@ func (suite *GlideTestSuite) TestXPendingFailures() { } }) } + +func (suite *GlideTestSuite) TestObjectEncoding() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // Test 1: Check object encoding for embstr + key := "{keyName}" + uuid.NewString() + value1 := "Hello" + t := suite.T() + suite.verifyOK(client.Set(key, value1)) + resultObjectEncoding, err := client.ObjectEncoding(key) + assert.Nil(t, err) + assert.Equal(t, "embstr", resultObjectEncoding.Value(), "The result should be embstr") + + // Test 2: Check object encoding command for non existing key + key2 := "{keyName}" + uuid.NewString() + resultDumpNull, err := client.ObjectEncoding(key2) + assert.Nil(t, err) + assert.Equal(t, "", resultDumpNull.Value()) + }) +} + +func (suite *GlideTestSuite) TestDumpRestore() { + suite.runWithDefaultClients(func(client api.BaseClient) { + // Test 1: Check restore command for deleted key and check value + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(client.Set(key, value)) + resultDump, err := client.Dump(key) + assert.Nil(t, err) + assert.NotNil(t, resultDump) + deletedCount, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), deletedCount) + result_test1, err := client.Restore(key, int64(0), resultDump.Value()) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test1.Value()) + resultGetRestoreKey, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGetRestoreKey.Value()) + + // Test 2: Check dump command for non existing key + key1 := "{keyName}" + uuid.NewString() + resultDumpNull, err := client.Dump(key1) + assert.Nil(t, err) + assert.Equal(t, "", resultDumpNull.Value()) + }) +} + +func (suite *GlideTestSuite) TestRestoreWithOptions() { + suite.runWithDefaultClients(func(client api.BaseClient) { + key := "testKey1_" + uuid.New().String() + value := "hello" + t := suite.T() + suite.verifyOK(client.Set(key, value)) + + resultDump, err := client.Dump(key) + assert.Nil(t, err) + assert.NotNil(t, resultDump) + + // Test 1: Check restore command with restoreOptions REPLACE modifier + deletedCount, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), deletedCount) + optsReplace := api.NewRestoreOptionsBuilder().SetReplace() + result_test1, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), optsReplace) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test1.Value()) + resultGetRestoreKey, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGetRestoreKey.Value()) + + // Test 2: Check restore command with restoreOptions ABSTTL modifier + delete_test2, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test2) + opts_test2 := api.NewRestoreOptionsBuilder().SetABSTTL() + result_test2, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test2) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test2.Value()) + resultGet_test2, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test2.Value()) + + // Test 3: Check restore command with restoreOptions FREQ modifier + delete_test3, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test3) + opts_test3 := api.NewRestoreOptionsBuilder().SetEviction(api.FREQ, 10) + result_test3, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test3) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test3.Value()) + resultGet_test3, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test3.Value()) + + // Test 4: Check restore command with restoreOptions IDLETIME modifier + delete_test4, err := client.Del([]string{key}) + assert.Nil(t, err) + assert.Equal(t, int64(1), delete_test4) + opts_test4 := api.NewRestoreOptionsBuilder().SetEviction(api.IDLETIME, 10) + result_test4, err := client.RestoreWithOptions(key, int64(0), resultDump.Value(), opts_test4) + assert.Nil(suite.T(), err) + assert.Equal(suite.T(), "OK", result_test4.Value()) + resultGet_test4, err := client.Get(key) + assert.Nil(t, err) + assert.Equal(t, value, resultGet_test4.Value()) + }) +}