Better KEYS tests

This commit is contained in:
tidwall 2022-09-23 16:12:32 -07:00
parent 295a9c45a8
commit 5bcef43894
4 changed files with 73 additions and 97 deletions

View File

@ -1,8 +1,7 @@
package server
import (
"bytes"
"strings"
"encoding/json"
"time"
"github.com/tidwall/resp"
@ -10,86 +9,59 @@ import (
"github.com/tidwall/tile38/internal/glob"
)
func (s *Server) cmdKeys(msg *Message) (res resp.Value, err error) {
// KEYS pattern
func (s *Server) cmdKEYS(msg *Message) (resp.Value, error) {
var start = time.Now()
vs := msg.Args[1:]
var pattern string
var ok bool
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
return NOMessage, errInvalidNumberOfArguments
}
if len(vs) != 0 {
return NOMessage, errInvalidNumberOfArguments
}
// >> Args
var wr = &bytes.Buffer{}
var once bool
if msg.OutputType == JSON {
wr.WriteString(`{"ok":true,"keys":[`)
args := msg.Args
if len(args) != 2 {
return retrerr(errInvalidNumberOfArguments)
}
var wild bool
if strings.Contains(pattern, "*") {
wild = true
}
var everything bool
var greater bool
var greaterPivot string
var vals []resp.Value
pattern := args[1]
iter := func(key string, col *collection.Collection) bool {
var match bool
// >> Operation
keys := []string{}
g := glob.Parse(pattern, false)
everything := g.Limits[0] == "" && g.Limits[1] == ""
if everything {
match = true
} else if greater {
if !strings.HasPrefix(key, greaterPivot) {
return false
}
match = true
} else {
match, _ = glob.Match(pattern, key)
}
s.cols.Scan(
func(key string, _ *collection.Collection) bool {
match, _ := glob.Match(pattern, key)
if match {
if once {
if msg.OutputType == JSON {
wr.WriteByte(',')
}
} else {
once = true
}
switch msg.OutputType {
case JSON:
wr.WriteString(jsonString(key))
case RESP:
vals = append(vals, resp.StringValue(key))
}
// If no more than one match is expected, stop searching
if !wild {
return false
}
keys = append(keys, key)
}
return true
},
)
} else {
s.cols.Ascend(g.Limits[0],
func(key string, _ *collection.Collection) bool {
if key > g.Limits[1] {
return false
}
match, _ := glob.Match(pattern, key)
if match {
keys = append(keys, key)
}
return true
},
)
}
// TODO: This can be further optimized by using glob.Parse and limits
if pattern == "*" {
everything = true
s.cols.Scan(iter)
} else if strings.HasSuffix(pattern, "*") {
greaterPivot = pattern[:len(pattern)-1]
if glob.IsGlob(greaterPivot) {
s.cols.Scan(iter)
} else {
greater = true
s.cols.Ascend(greaterPivot, iter)
}
} else {
s.cols.Scan(iter)
}
// >> Response
if msg.OutputType == JSON {
wr.WriteString(`],"elapsed":"` + time.Since(start).String() + "\"}")
return resp.StringValue(wr.String()), nil
data, _ := json.Marshal(keys)
return resp.StringValue(`{"ok":true,"keys":` + string(data) +
`,"elapsed":"` + time.Since(start).String() + `"}`), nil
}
var vals []resp.Value
for _, key := range keys {
vals = append(vals, resp.StringValue(key))
}
return resp.ArrayValue(vals), nil
}

View File

@ -636,7 +636,7 @@ func (s *Server) commandInScript(msg *Message) (
case "type":
res, err = s.cmdTYPE(msg)
case "keys":
res, err = s.cmdKeys(msg)
res, err = s.cmdKEYS(msg)
case "test":
res, err = s.cmdTest(msg)
case "server":

View File

@ -1110,7 +1110,7 @@ func (s *Server) command(msg *Message, client *Client) (
case "type":
res, err = s.cmdTYPE(msg)
case "keys":
res, err = s.cmdKeys(msg)
res, err = s.cmdKEYS(msg)
case "output":
res, err = s.cmdOutput(msg)
case "aof":

View File

@ -225,28 +225,30 @@ func keys_GET_test(mc *mockServer) error {
)
}
func keys_KEYS_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"SET", "mykey11", "myid4", "STRING", "value"}, {"OK"},
{"SET", "mykey22", "myid2", "HASH", "9my5xp7"}, {"OK"},
{"SET", "mykey22", "myid1", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`}, {"OK"},
{"SET", "mykey11", "myid3", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`}, {"OK"},
{"SET", "mykey42", "myid2", "HASH", "9my5xp7"}, {"OK"},
{"SET", "mykey31", "myid4", "STRING", "value"}, {"OK"},
{"SET", "mykey310", "myid5", "STRING", "value"}, {"OK"},
{"KEYS", "*"}, {"[mykey11 mykey22 mykey31 mykey310 mykey42]"},
{"KEYS", "*key*"}, {"[mykey11 mykey22 mykey31 mykey310 mykey42]"},
{"KEYS", "mykey*"}, {"[mykey11 mykey22 mykey31 mykey310 mykey42]"},
{"KEYS", "mykey4*"}, {"[mykey42]"},
{"KEYS", "mykey*1"}, {"[mykey11 mykey31]"},
{"KEYS", "mykey*1*"}, {"[mykey11 mykey31 mykey310]"},
{"KEYS", "mykey*10"}, {"[mykey310]"},
{"KEYS", "mykey*2"}, {"[mykey22 mykey42]"},
{"KEYS", "*2"}, {"[mykey22 mykey42]"},
{"KEYS", "*1*"}, {"[mykey11 mykey31 mykey310]"},
{"KEYS", "mykey"}, {"[]"},
{"KEYS", "mykey31"}, {"[mykey31]"},
{"KEYS", "mykey[^3]*"}, {"[mykey11 mykey22 mykey42]"},
})
return mc.DoBatch(
Do("SET", "mykey11", "myid4", "STRING", "value").OK(),
Do("SET", "mykey22", "myid2", "HASH", "9my5xp7").OK(),
Do("SET", "mykey22", "myid1", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`).OK(),
Do("SET", "mykey11", "myid3", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`).OK(),
Do("SET", "mykey42", "myid2", "HASH", "9my5xp7").OK(),
Do("SET", "mykey31", "myid4", "STRING", "value").OK(),
Do("SET", "mykey310", "myid5", "STRING", "value").OK(),
Do("KEYS", "*").Str("[mykey11 mykey22 mykey31 mykey310 mykey42]"),
Do("KEYS", "*key*").Str("[mykey11 mykey22 mykey31 mykey310 mykey42]"),
Do("KEYS", "mykey*").Str("[mykey11 mykey22 mykey31 mykey310 mykey42]"),
Do("KEYS", "mykey4*").Str("[mykey42]"),
Do("KEYS", "mykey*1").Str("[mykey11 mykey31]"),
Do("KEYS", "mykey*1*").Str("[mykey11 mykey31 mykey310]"),
Do("KEYS", "mykey*10").Str("[mykey310]"),
Do("KEYS", "mykey*2").Str("[mykey22 mykey42]"),
Do("KEYS", "*2").Str("[mykey22 mykey42]"),
Do("KEYS", "*1*").Str("[mykey11 mykey31 mykey310]"),
Do("KEYS", "mykey").Str("[]"),
Do("KEYS", "mykey31").Str("[mykey31]"),
Do("KEYS", "mykey[^3]*").Str("[mykey11 mykey22 mykey42]"),
Do("KEYS").Err("wrong number of arguments for 'keys' command"),
Do("KEYS", "*").JSON().Str(`{"ok":true,"keys":["mykey11","mykey22","mykey31","mykey310","mykey42"]}`),
)
}
func keys_PERSIST_test(mc *mockServer) error {
return mc.DoBatch(
@ -371,6 +373,8 @@ func keys_STATS_test(mc *mockServer) error {
Do("DEL", "mykey", "myid2").Str("1"),
Do("STATS", "mykey").Str("[nil]"),
Do("STATS", "mykey", "mykey2").Str("[nil nil]"),
Do("STATS", "mykey").Str("[nil]"),
Do("STATS").Err(`wrong number of arguments for 'stats' command`),
)
}
func keys_TTL_test(mc *mockServer) error {