Add RENAME and RENAMENX commands.

This commit is contained in:
Alex Roitman 2018-12-27 17:15:53 -08:00
parent 1eee8ee1a1
commit 01a7dda2a1
6 changed files with 117 additions and 4 deletions

View File

@ -454,6 +454,62 @@ func (server *Server) cmdDrop(msg *Message) (res resp.Value, d commandDetails, e
return return
} }
func (server *Server) cmdRename(msg *Message, nx bool) (res resp.Value, d commandDetails, err error) {
start := time.Now()
vs := msg.Args[1:]
var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments
return
}
if vs, d.newKey, ok = tokenval(vs); !ok || d.newKey == "" {
err = errInvalidNumberOfArguments
return
}
if len(vs) != 0 {
err = errInvalidNumberOfArguments
return
}
for _, h := range server.hooks {
if h.Key == d.key || h.Key == d.newKey {
err = errKeyHasHooksSet
return
}
}
col := server.getCol(d.key)
if col == nil {
err = errKeyNotFound
return
}
d.command = "rename"
d.updated = true
newCol := server.getCol(d.newKey)
if newCol != nil {
if nx {
d.updated = false
} else {
server.deleteCol(d.newKey)
}
}
if d.updated {
server.deleteCol(d.key)
server.setCol(d.newKey, col)
server.moveKeyExpires(d.key, d.newKey)
}
d.timestamp = time.Now()
switch msg.OutputType {
case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case RESP:
if d.updated {
res = resp.IntegerValue(1)
} else {
res = resp.IntegerValue(0)
}
}
return
}
func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails, err error) { func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetails, err error) {
start := time.Now() start := time.Now()
vs := msg.Args[1:] vs := msg.Args[1:]

View File

@ -64,6 +64,13 @@ func (c *Server) clearKeyExpires(key string) {
delete(c.expires, key) delete(c.expires, key)
} }
// moveKeyExpires moves all items that are marked as expires from a key to a newKey.
func (c *Server) moveKeyExpires(key, newKey string) {
val := c.expires[key]
delete(c.expires, key)
c.expires[newKey] = val
}
// expireAt marks an item as expires at a specific time. // expireAt marks an item as expires at a specific time.
func (c *Server) expireAt(key, id string, at time.Time) { func (c *Server) expireAt(key, id string, at time.Time) {
m := c.expires[key] m := c.expires[key]

View File

@ -575,6 +575,10 @@ func (c *Server) commandInScript(msg *Message) (
res, d, err = c.cmdDrop(msg) res, d, err = c.cmdDrop(msg)
case "expire": case "expire":
res, d, err = c.cmdExpire(msg) res, d, err = c.cmdExpire(msg)
case "rename":
res, d, err = c.cmdRename(msg, false)
case "renamenx":
res, d, err = c.cmdRename(msg, true)
case "persist": case "persist":
res, d, err = c.cmdPersist(msg) res, d, err = c.cmdPersist(msg)
case "ttl": case "ttl":
@ -642,7 +646,8 @@ func (c *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) {
switch msg.Command() { switch msg.Command() {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel": case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel",
"rename", "renamenx":
// write operations // write operations
write = true write = true
if c.config.followHost() != "" { if c.config.followHost() != "" {
@ -678,7 +683,9 @@ func (c *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel": case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel",
"rename", "renamenx":
// write operations
return resp.NullValue(), errReadOnly return resp.NullValue(), errReadOnly
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search",
@ -704,7 +711,8 @@ func (c *Server) luaTile38NonAtomic(msg *Message) (resp.Value, error) {
switch msg.Command() { switch msg.Command() {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel": case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel",
"rename", "renamenx":
// write operations // write operations
write = true write = true
c.mu.Lock() c.mu.Lock()

View File

@ -49,6 +49,7 @@ const (
type commandDetails struct { type commandDetails struct {
command string // client command, like "SET" or "DEL" command string // client command, like "SET" or "DEL"
key, id string // collection key and object id of object key, id string // collection key and object id of object
newKey string // new key, for RENAME command
fmap map[string]int // map of field names to value indexes fmap map[string]int // map of field names to value indexes
obj geojson.Object // new object obj geojson.Object // new object
fields []float64 // array of field values fields []float64 // array of field values
@ -934,7 +935,7 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
case "set", "del", "drop", "fset", "flushdb", case "set", "del", "drop", "fset", "flushdb",
"setchan", "pdelchan", "delchan", "setchan", "pdelchan", "delchan",
"sethook", "pdelhook", "delhook", "sethook", "pdelhook", "delhook",
"expire", "persist", "jset", "pdel": "expire", "persist", "jset", "pdel", "rename", "renamenx":
// write operations // write operations
write = true write = true
server.mu.Lock() server.mu.Lock()
@ -1072,6 +1073,10 @@ func (server *Server) command(msg *Message, client *Client) (
res, d, err = server.cmdDrop(msg) res, d, err = server.cmdDrop(msg)
case "flushdb": case "flushdb":
res, d, err = server.cmdFlushDB(msg) res, d, err = server.cmdFlushDB(msg)
case "rename":
res, d, err = server.cmdRename(msg, false)
case "renamenx":
res, d, err = server.cmdRename(msg, true)
case "sethook": case "sethook":
res, d, err = server.cmdSetHook(msg, false) res, d, err = server.cmdSetHook(msg, false)

View File

@ -17,6 +17,7 @@ var errKeyNotFound = errors.New("key not found")
var errIDNotFound = errors.New("id not found") var errIDNotFound = errors.New("id not found")
var errIDAlreadyExists = errors.New("id already exists") var errIDAlreadyExists = errors.New("id already exists")
var errPathNotFound = errors.New("path not found") var errPathNotFound = errors.New("path not found")
var errKeyHasHooksSet = errors.New("key has hooks set")
func errInvalidArgument(arg string) error { func errInvalidArgument(arg string) error {
return fmt.Errorf("invalid argument '%s'", arg) return fmt.Errorf("invalid argument '%s'", arg)

View File

@ -18,6 +18,8 @@ func subTestKeys(t *testing.T, mc *mockServer) {
runStep(t, mc, "BOUNDS", keys_BOUNDS_test) runStep(t, mc, "BOUNDS", keys_BOUNDS_test)
runStep(t, mc, "DEL", keys_DEL_test) runStep(t, mc, "DEL", keys_DEL_test)
runStep(t, mc, "DROP", keys_DROP_test) runStep(t, mc, "DROP", keys_DROP_test)
runStep(t, mc, "RENAME", keys_RENAME_test)
runStep(t, mc, "RENAMENX", keys_RENAMENX_test)
runStep(t, mc, "EXPIRE", keys_EXPIRE_test) runStep(t, mc, "EXPIRE", keys_EXPIRE_test)
runStep(t, mc, "FSET", keys_FSET_test) runStep(t, mc, "FSET", keys_FSET_test)
runStep(t, mc, "GET", keys_GET_test) runStep(t, mc, "GET", keys_GET_test)
@ -65,6 +67,40 @@ func keys_DROP_test(mc *mockServer) error {
{"SCAN", "mykey", "COUNT"}, {0}, {"SCAN", "mykey", "COUNT"}, {0},
}) })
} }
func keys_RENAME_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid1", "HASH", "9my5xp7"}, {"OK"},
{"SET", "mykey", "myid2", "HASH", "9my5xp8"}, {"OK"},
{"SCAN", "mykey", "COUNT"}, {2},
{"RENAME", "mykey", "mynewkey"}, {1},
{"SCAN", "mykey", "COUNT"}, {0},
{"SCAN", "mynewkey", "COUNT"}, {2},
{"SET", "mykey", "myid3", "HASH", "9my5xp7"}, {"OK"},
{"RENAME", "mykey", "mynewkey"}, {1},
{"SCAN", "mykey", "COUNT"}, {0},
{"SCAN", "mynewkey", "COUNT"}, {1},
{"RENAME", "foo", "mynewkey"}, {"ERR key not found"},
{"SCAN", "mynewkey", "COUNT"}, {1},
})
}
func keys_RENAMENX_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid1", "HASH", "9my5xp7"}, {"OK"},
{"SET", "mykey", "myid2", "HASH", "9my5xp8"}, {"OK"},
{"SCAN", "mykey", "COUNT"}, {2},
{"RENAMENX", "mykey", "mynewkey"}, {1},
{"SCAN", "mykey", "COUNT"}, {0},
{"DROP", "mykey"}, {0},
{"SCAN", "mykey", "COUNT"}, {0},
{"SCAN", "mynewkey", "COUNT"}, {2},
{"SET", "mykey", "myid3", "HASH", "9my5xp7"}, {"OK"},
{"RENAMENX", "mykey", "mynewkey"}, {0},
{"SCAN", "mykey", "COUNT"}, {1},
{"SCAN", "mynewkey", "COUNT"}, {2},
{"RENAMENX", "foo", "mynewkey"}, {"ERR key not found"},
{"SCAN", "mynewkey", "COUNT"}, {2},
})
}
func keys_EXPIRE_test(mc *mockServer) error { func keys_EXPIRE_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"}, {"SET", "mykey", "myid", "STRING", "value"}, {"OK"},