mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-26 09:20:24 +08:00
File diff suppressed because it is too large
Load Diff
48
docs/docs/commands/generic/move.mdx
Normal file
48
docs/docs/commands/generic/move.mdx
Normal file
@@ -0,0 +1,48 @@
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# MOVE
|
||||
|
||||
### Syntax
|
||||
```
|
||||
MOVE key database
|
||||
```
|
||||
|
||||
### Module
|
||||
<span className="acl-category">generic</span>
|
||||
|
||||
### Categories
|
||||
<span className="acl-category">fast</span>
|
||||
<span className="acl-category">keyspace</span>
|
||||
<span className="acl-category">write</span>
|
||||
|
||||
### Description
|
||||
Move key from currently selected database to specified destination database. Returns 1 if successful, if
|
||||
key already exists in the destination database, or key does not exist in the source database, it does nothing and returns 0.
|
||||
|
||||
### Examples
|
||||
|
||||
<Tabs
|
||||
defaultValue="go"
|
||||
values={[
|
||||
{ label: 'Go (Embedded)', value: 'go', },
|
||||
{ label: 'CLI', value: 'cli', },
|
||||
]}
|
||||
>
|
||||
<TabItem value="go">
|
||||
Move the key to database 1:
|
||||
```go
|
||||
db, err := sugardb.NewSugarDB()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
value, err := db.Move("key", 1)
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="cli">
|
||||
Move the key to database 1:
|
||||
```
|
||||
> MOVE key 1
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -15,6 +15,7 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -869,6 +870,51 @@ func handleObjIdleTime(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("+%v\r\n", idletime)), nil
|
||||
}
|
||||
|
||||
func handleMove(params internal.HandlerFuncParams) ([]byte, error) {
|
||||
keys, err := moveKeyFunc(params.Command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := keys.WriteKeys[0]
|
||||
|
||||
// get key, destination db and current db
|
||||
values := params.GetValues(params.Context, []string{key})
|
||||
value, _ := values[key]
|
||||
if value == nil {
|
||||
return []byte(fmt.Sprintf("+%v\r\n", 0)), nil
|
||||
}
|
||||
|
||||
newdb, err := strconv.Atoi(params.Command[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if newdb < 0 {
|
||||
return nil, errors.New("database must be >= 0")
|
||||
}
|
||||
|
||||
// see if key exists in destination db, if not set key there
|
||||
ctx := context.WithValue(params.Context, "Database", newdb)
|
||||
keyExists := params.KeysExist(ctx, keys.WriteKeys)[key]
|
||||
if !keyExists {
|
||||
|
||||
err = params.SetValues(ctx, map[string]interface{}{key: value})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove key from source db
|
||||
err = params.DeleteKey(params.Context, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("+%v\r\n", 1)), nil
|
||||
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("+%v\r\n", 0)), nil
|
||||
}
|
||||
|
||||
func Commands() []internal.Command {
|
||||
return []internal.Command{
|
||||
{
|
||||
@@ -1209,5 +1255,14 @@ The command is only available when the maxmemory-policy configuration directive
|
||||
KeyExtractionFunc: objIdleTimeKeyFunc,
|
||||
HandlerFunc: handleObjIdleTime,
|
||||
},
|
||||
{
|
||||
Command: "move",
|
||||
Module: constants.GenericModule,
|
||||
Categories: []string{constants.KeyspaceCategory, constants.WriteCategory, constants.FastCategory},
|
||||
Description: `(MOVE key db) Moves a key from the selected database to the specified database.`,
|
||||
Sync: true,
|
||||
KeyExtractionFunc: moveKeyFunc,
|
||||
HandlerFunc: handleMove,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3332,6 +3332,145 @@ func Test_Generic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Test_HandleMOVE", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
port, err := internal.GetFreePort()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mockServer, err := sugardb.NewSugarDB(
|
||||
sugardb.WithConfig(config.Config{
|
||||
BindAddr: "localhost",
|
||||
Port: uint16(port),
|
||||
DataDir: "",
|
||||
EvictionPolicy: constants.NoEviction,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
mockServer.Start()
|
||||
}()
|
||||
t.Cleanup(func() {
|
||||
mockServer.ShutDown()
|
||||
})
|
||||
|
||||
conn, err := internal.GetConnection("localhost", port)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
client := resp.NewConn(conn)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value string
|
||||
preset bool
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "1. Move key.",
|
||||
key: "MoveKey1",
|
||||
value: "value1",
|
||||
preset: true,
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "2. Move key that already exists in new database.",
|
||||
key: "MoveKey1",
|
||||
value: "value1",
|
||||
preset: false,
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
name: "3. Move key that doesn't exist in current database.",
|
||||
key: "MoveKey3",
|
||||
value: "value3",
|
||||
preset: false,
|
||||
expected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Log(tt.name)
|
||||
|
||||
// Preset the values
|
||||
if tt.preset {
|
||||
err = client.WriteArray([]resp.Value{resp.StringValue("SET"), resp.StringValue(tt.key), resp.StringValue(tt.value)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
res, _, err := client.ReadValue()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(res.String(), "ok") {
|
||||
t.Errorf("expected preset response to be \"OK\", got %s", res.String())
|
||||
}
|
||||
}
|
||||
|
||||
if err = client.WriteArray([]resp.Value{resp.StringValue("MOVE"), resp.StringValue(tt.key), resp.StringValue("1")}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
res, _, err := client.ReadValue()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if res.Integer() != tt.expected {
|
||||
t.Errorf("expected value %v, got %v", tt.expected, res.Integer())
|
||||
t.Error(mockServer.Get(tt.key))
|
||||
}
|
||||
|
||||
// check other db
|
||||
if tt.expected == 1 {
|
||||
|
||||
actual, err := mockServer.Get(tt.key)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if actual != "" {
|
||||
t.Errorf("when verifying key was moved from the original db, expected to get empty string but got %q", actual)
|
||||
}
|
||||
|
||||
err = mockServer.SelectDB(1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
actual, err = mockServer.Get(tt.key)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if actual != tt.value {
|
||||
t.Errorf("when verifying key was moved to the new db, expected to get value %q, but got %q", tt.value, actual)
|
||||
}
|
||||
|
||||
err = mockServer.SelectDB(0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Certain commands will need to be tested in a server with an eviction policy.
|
||||
|
||||
@@ -267,3 +267,14 @@ func objIdleTimeKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error)
|
||||
WriteKeys: make([]string, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func moveKeyFunc(cmd []string) (internal.KeyExtractionFuncResult, error) {
|
||||
if len(cmd) != 3 {
|
||||
return internal.KeyExtractionFuncResult{}, errors.New(constants.WrongArgsResponse)
|
||||
}
|
||||
return internal.KeyExtractionFuncResult{
|
||||
Channels: make([]string, 0),
|
||||
ReadKeys: make([]string, 0),
|
||||
WriteKeys: []string{cmd[1]},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -623,7 +623,7 @@ func (server *SugarDB) GetDel(key string) (string, error) {
|
||||
//
|
||||
// `option` - GetExOption - one of EX, PX, EXAT, PXAT, PERSIST. Can be nil.
|
||||
//
|
||||
// `unixtime` - Number of seconds or miliseconds from now.
|
||||
// `unixtime` - int - Number of seconds or miliseconds from now.
|
||||
//
|
||||
// Returns: A string representing the value at the specified key. If the value does not exist, an empty string is returned.
|
||||
func (server *SugarDB) GetEx(key string, option GetExOption, unixtime int) (string, error) {
|
||||
@@ -718,3 +718,21 @@ func (server *SugarDB) Type(key string) (string, error) {
|
||||
}
|
||||
return internal.ParseStringResponse(b)
|
||||
}
|
||||
|
||||
// Move key from currently selected database to specified destination database and return 1.
|
||||
// When key already exists in the destination database, or it does not exist in the source database, it does nothing and returns 0.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// `key` - string - the key that should be moved.
|
||||
//
|
||||
// `destinationDB` - int - the database the key should be moved to.
|
||||
//
|
||||
// Returns: 1 if successful, 0 if unsuccessful.
|
||||
func (server *SugarDB) Move(key string, destinationDB int) (int, error) {
|
||||
b, err := server.handleCommand(server.context, internal.EncodeCommand([]string{"Move", key, strconv.Itoa(destinationDB)}), nil, false, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return internal.ParseIntegerResponse(b)
|
||||
}
|
||||
|
||||
@@ -1825,3 +1825,49 @@ func TestSugarDB_TYPE(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSugarDB_MOVE(t *testing.T) {
|
||||
server := createSugarDB()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
presetValue interface{}
|
||||
key string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "1. Move key successfully",
|
||||
presetValue: "value1",
|
||||
key: "key1",
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "2. Attempt to move key, unsuccessful",
|
||||
presetValue: nil,
|
||||
key: "key2",
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Log(tt.name)
|
||||
if tt.presetValue != nil {
|
||||
err := presetValue(server, context.Background(), tt.key, tt.presetValue)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
got, err := server.Move(tt.key, 1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got != tt.want {
|
||||
t.Errorf("MOVE() got %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user