diff --git a/internal/server/keys.go b/internal/server/keys.go index 94eaf199..095a0686 100644 --- a/internal/server/keys.go +++ b/internal/server/keys.go @@ -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 - if everything { - match = true - } else if greater { - if !strings.HasPrefix(key, greaterPivot) { - return false - } - match = true - } else { - match, _ = glob.Match(pattern, key) - } - if match { - if once { - if msg.OutputType == JSON { - wr.WriteByte(',') + // >> Operation + + keys := []string{} + g := glob.Parse(pattern, false) + everything := g.Limits[0] == "" && g.Limits[1] == "" + if everything { + s.cols.Scan( + func(key string, _ *collection.Collection) bool { + match, _ := glob.Match(pattern, key) + if match { + keys = append(keys, key) } - } 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 - } - } - 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) - } + return true + }, + ) } else { - s.cols.Scan(iter) + 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 + }, + ) } + + // >> 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 } diff --git a/internal/server/scripts.go b/internal/server/scripts.go index d1948d21..acbb3310 100644 --- a/internal/server/scripts.go +++ b/internal/server/scripts.go @@ -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": diff --git a/internal/server/server.go b/internal/server/server.go index 7b1a2781..0f8d7211 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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": diff --git a/tests/keys_test.go b/tests/keys_test.go index 7a698e09..9361e8c6 100644 --- a/tests/keys_test.go +++ b/tests/keys_test.go @@ -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 {