mirror of https://github.com/tidwall/tile38.git
Merge pull request #395 from rshura/rename
Add RENAME and RENAMENX commands.
This commit is contained in:
commit
c63effe54a
|
@ -282,6 +282,38 @@
|
||||||
"since": "1.0.0",
|
"since": "1.0.0",
|
||||||
"group": "keys"
|
"group": "keys"
|
||||||
},
|
},
|
||||||
|
"RENAME": {
|
||||||
|
"summary": "Rename a key to be stored under a different name.",
|
||||||
|
"complexity": "O(1)",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newkey",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"since": "1.14.5",
|
||||||
|
"group": "keys"
|
||||||
|
},
|
||||||
|
"RENAMENX": {
|
||||||
|
"summary": "Rename a key to be stored under a different name, if a new key does not exist.",
|
||||||
|
"complexity": "O(1)",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "key",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newkey",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"since": "1.14.5",
|
||||||
|
"group": "keys"
|
||||||
|
},
|
||||||
"KEYS": {
|
"KEYS": {
|
||||||
"summary": "Finds all keys matching the given pattern",
|
"summary": "Finds all keys matching the given pattern",
|
||||||
"complexity": "O(N) where N is the number of keys in the database",
|
"complexity": "O(N) where N is the number of keys in the database",
|
||||||
|
@ -1557,7 +1589,7 @@
|
||||||
},
|
},
|
||||||
"EVAL":{
|
"EVAL":{
|
||||||
"summary": "Evaluates a Lua script",
|
"summary": "Evaluates a Lua script",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "script",
|
"name": "script",
|
||||||
|
@ -1585,7 +1617,7 @@
|
||||||
},
|
},
|
||||||
"EVALSHA":{
|
"EVALSHA":{
|
||||||
"summary": "Evaluates a Lua script cached on the server by its SHA1 digest",
|
"summary": "Evaluates a Lua script cached on the server by its SHA1 digest",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "sha1",
|
"name": "sha1",
|
||||||
|
@ -1613,7 +1645,7 @@
|
||||||
},
|
},
|
||||||
"EVALRO":{
|
"EVALRO":{
|
||||||
"summary": "Evaluates a read-only Lua script",
|
"summary": "Evaluates a read-only Lua script",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "script",
|
"name": "script",
|
||||||
|
@ -1641,7 +1673,7 @@
|
||||||
},
|
},
|
||||||
"EVALROSHA":{
|
"EVALROSHA":{
|
||||||
"summary": "Evaluates a read-only Lua script cached on the server by its SHA1 digest",
|
"summary": "Evaluates a read-only Lua script cached on the server by its SHA1 digest",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "script",
|
"name": "script",
|
||||||
|
@ -1669,7 +1701,7 @@
|
||||||
},
|
},
|
||||||
"EVALNA":{
|
"EVALNA":{
|
||||||
"summary": "Evaluates a Lua script in a non-atomic fashion",
|
"summary": "Evaluates a Lua script in a non-atomic fashion",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "script",
|
"name": "script",
|
||||||
|
@ -1697,7 +1729,7 @@
|
||||||
},
|
},
|
||||||
"EVALNASHA":{
|
"EVALNASHA":{
|
||||||
"summary": "Evaluates, in a non-atomic fashion, a Lua script cached on the server by its SHA1 digest",
|
"summary": "Evaluates, in a non-atomic fashion, a Lua script cached on the server by its SHA1 digest",
|
||||||
"complecxity": "Depends on the evaluated script",
|
"complexity": "Depends on the evaluated script",
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"name": "sha1",
|
"name": "sha1",
|
||||||
|
|
|
@ -454,6 +454,65 @@ 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
|
||||||
|
}
|
||||||
|
col := server.getCol(d.key)
|
||||||
|
if col == nil {
|
||||||
|
err = errKeyNotFound
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, h := range server.hooks {
|
||||||
|
if h.Key == d.key || h.Key == d.newKey {
|
||||||
|
err = errKeyHasHooksSet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.command = "rename"
|
||||||
|
newCol := server.getCol(d.newKey)
|
||||||
|
if newCol == nil {
|
||||||
|
d.updated = true
|
||||||
|
} else if nx {
|
||||||
|
d.updated = false
|
||||||
|
} else {
|
||||||
|
server.deleteCol(d.newKey)
|
||||||
|
server.clearKeyExpires(d.newKey)
|
||||||
|
d.updated = true
|
||||||
|
}
|
||||||
|
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 !nx {
|
||||||
|
res = resp.SimpleStringValue("OK")
|
||||||
|
} else 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:]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"}, {"OK"},
|
||||||
|
{"SCAN", "mykey", "COUNT"}, {0},
|
||||||
|
{"SCAN", "mynewkey", "COUNT"}, {2},
|
||||||
|
{"SET", "mykey", "myid3", "HASH", "9my5xp7"}, {"OK"},
|
||||||
|
{"RENAME", "mykey", "mynewkey"}, {"OK"},
|
||||||
|
{"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"},
|
||||||
|
|
Loading…
Reference in New Issue