Better DEL/PDEL/TYPE tests

This commit is contained in:
tidwall 2022-09-23 09:04:01 -07:00
parent ef95f04aca
commit db380a4fee
7 changed files with 156 additions and 129 deletions

View File

@ -73,33 +73,38 @@ func (s *Server) cmdBOUNDS(msg *Message) (resp.Value, error) {
return vals[0], nil return vals[0], nil
} }
func (s *Server) cmdType(msg *Message) (resp.Value, error) { // TYPE key
// undocumented return "none" or "hash"
func (s *Server) cmdTYPE(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Args[1:]
var ok bool // >> Args
var key string
if _, key, ok = tokenval(vs); !ok || key == "" { args := msg.Args
return NOMessage, errInvalidNumberOfArguments if len(args) != 2 {
return retrerr(errInvalidNumberOfArguments)
} }
key := args[1]
// >> Operation
col, _ := s.cols.Get(key) col, _ := s.cols.Get(key)
if col == nil { if col == nil {
if msg.OutputType == RESP { if msg.OutputType == RESP {
return resp.SimpleStringValue("none"), nil return resp.SimpleStringValue("none"), nil
} }
return NOMessage, errKeyNotFound return retrerr(errKeyNotFound)
} }
// >> Response
typ := "hash" typ := "hash"
switch msg.OutputType { if msg.OutputType == JSON {
case JSON: return resp.StringValue(`{"ok":true,"type":` + jsonString(typ) +
return resp.StringValue(`{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Since(start).String() + "\"}"), nil `,"elapsed":"` + time.Since(start).String() + "\"}"), nil
case RESP:
return resp.SimpleStringValue(typ), nil
} }
return NOMessage, nil return resp.SimpleStringValue(typ), nil
} }
func (s *Server) cmdGet(msg *Message) (resp.Value, error) { func (s *Server) cmdGet(msg *Message) (resp.Value, error) {
@ -262,7 +267,7 @@ func (s *Server) cmdGet(msg *Message) (resp.Value, error) {
} }
// DEL key id [ERRON404] // DEL key id [ERRON404]
func (s *Server) cmdDel(msg *Message) (resp.Value, commandDetails, error) { func (s *Server) cmdDEL(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now() start := time.Now()
// >> Args // >> Args
@ -329,85 +334,75 @@ func (s *Server) cmdDel(msg *Message) (resp.Value, commandDetails, error) {
return res, d, nil return res, d, nil
} }
func (s *Server) cmdPdel(msg *Message) (res resp.Value, d commandDetails, err error) { // PDEL key pattern
func (s *Server) cmdPDEL(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now() 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.pattern, ok = tokenval(vs); !ok || d.pattern == "" {
err = errInvalidNumberOfArguments
return
}
if len(vs) != 0 {
err = errInvalidNumberOfArguments
return
}
now := time.Now()
iter := func(o *object.Object) bool {
if match, _ := glob.Match(d.pattern, o.ID()); match {
d.children = append(d.children, &commandDetails{
command: "del",
updated: true,
timestamp: now,
key: d.key,
obj: o,
})
}
return true
}
var expired int // >> Args
col, _ := s.cols.Get(d.key)
args := msg.Args
if len(args) != 3 {
return retwerr(errInvalidNumberOfArguments)
}
key := args[1]
pattern := args[2]
// >> Operation
now := time.Now()
var children []*commandDetails
col, _ := s.cols.Get(key)
if col != nil { if col != nil {
g := glob.Parse(d.pattern, false) g := glob.Parse(pattern, false)
var ids []string
iter := func(o *object.Object) bool {
if match, _ := glob.Match(pattern, o.ID()); match {
ids = append(ids, o.ID())
}
return true
}
if g.Limits[0] == "" && g.Limits[1] == "" { if g.Limits[0] == "" && g.Limits[1] == "" {
col.Scan(false, nil, msg.Deadline, iter) col.Scan(false, nil, msg.Deadline, iter)
} else { } else {
col.ScanRange(g.Limits[0], g.Limits[1], false, nil, msg.Deadline, iter) col.ScanRange(g.Limits[0], g.Limits[1],
false, nil, msg.Deadline, iter)
} }
var atLeastOneNotDeleted bool for _, id := range ids {
for i, dc := range d.children { obj := col.Delete(id)
old := col.Delete(dc.obj.ID()) children = append(children, &commandDetails{
if old == nil { command: "del",
d.children[i].command = "?" updated: true,
atLeastOneNotDeleted = true timestamp: now,
} else { key: key,
dc.obj = old obj: obj,
d.children[i] = dc })
} s.groupDisconnectObject(key, id)
s.groupDisconnectObject(dc.key, dc.obj.ID())
}
if atLeastOneNotDeleted {
var nchildren []*commandDetails
for _, dc := range d.children {
if dc.command == "del" {
nchildren = append(nchildren, dc)
}
}
d.children = nchildren
} }
if col.Count() == 0 { if col.Count() == 0 {
s.cols.Delete(d.key) s.cols.Delete(key)
} }
} }
// >> Response
var d commandDetails
var res resp.Value
d.command = "pdel" d.command = "pdel"
d.children = children
d.key = key
d.pattern = pattern
d.updated = len(d.children) > 0 d.updated = len(d.children) > 0
d.timestamp = now d.timestamp = now
d.parent = true d.parent = true
switch msg.OutputType { switch msg.OutputType {
case JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP: case RESP:
total := len(d.children) - expired res = resp.IntegerValue(len(d.children))
if total < 0 {
total = 0
}
res = resp.IntegerValue(total)
} }
return return res, d, nil
} }
func (s *Server) cmdDrop(msg *Message) (res resp.Value, d commandDetails, err error) { func (s *Server) cmdDrop(msg *Message) (res resp.Value, d commandDetails, err error) {

View File

@ -42,7 +42,7 @@ func (s *Server) backgroundExpireObjects(now time.Time) {
return true return true
}) })
for _, msg := range msgs { for _, msg := range msgs {
_, d, err := s.cmdDel(msg) _, d, err := s.cmdDEL(msg)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -596,9 +596,9 @@ func (s *Server) commandInScript(msg *Message) (
case "fset": case "fset":
res, d, err = s.cmdFSET(msg) res, d, err = s.cmdFSET(msg)
case "del": case "del":
res, d, err = s.cmdDel(msg) res, d, err = s.cmdDEL(msg)
case "pdel": case "pdel":
res, d, err = s.cmdPdel(msg) res, d, err = s.cmdPDEL(msg)
case "drop": case "drop":
res, d, err = s.cmdDrop(msg) res, d, err = s.cmdDrop(msg)
case "expire": case "expire":
@ -634,7 +634,7 @@ func (s *Server) commandInScript(msg *Message) (
case "jdel": case "jdel":
res, d, err = s.cmdJdel(msg) res, d, err = s.cmdJdel(msg)
case "type": case "type":
res, err = s.cmdType(msg) res, err = s.cmdTYPE(msg)
case "keys": case "keys":
res, err = s.cmdKeys(msg) res, err = s.cmdKeys(msg)
case "test": case "test":

View File

@ -1020,9 +1020,9 @@ func (s *Server) command(msg *Message, client *Client) (
case "fset": case "fset":
res, d, err = s.cmdFSET(msg) res, d, err = s.cmdFSET(msg)
case "del": case "del":
res, d, err = s.cmdDel(msg) res, d, err = s.cmdDEL(msg)
case "pdel": case "pdel":
res, d, err = s.cmdPdel(msg) res, d, err = s.cmdPDEL(msg)
case "drop": case "drop":
res, d, err = s.cmdDrop(msg) res, d, err = s.cmdDrop(msg)
case "flushdb": case "flushdb":
@ -1106,7 +1106,7 @@ func (s *Server) command(msg *Message, client *Client) (
case "jdel": case "jdel":
res, d, err = s.cmdJdel(msg) res, d, err = s.cmdJdel(msg)
case "type": case "type":
res, err = s.cmdType(msg) res, err = s.cmdTYPE(msg)
case "keys": case "keys":
res, err = s.cmdKeys(msg) res, err = s.cmdKeys(msg)
case "output": case "output":

View File

@ -11,4 +11,6 @@ go tool cover -html=/tmp/coverage.out -o /tmp/coverage.html
echo "details: file:///tmp/coverage.html" echo "details: file:///tmp/coverage.html"
cd .. cd ..
go test $(go list ./... | grep -v /vendor/ | grep -v /tests) if [[ "$GOTEST" == "" ]]; then
go test $(go list ./... | grep -v /vendor/ | grep -v /tests)
fi

View File

@ -30,38 +30,50 @@ func subTestKeys(t *testing.T, mc *mockServer) {
runStep(t, mc, "FIELDS", keys_FIELDS_test) runStep(t, mc, "FIELDS", keys_FIELDS_test)
runStep(t, mc, "WHEREIN", keys_WHEREIN_test) runStep(t, mc, "WHEREIN", keys_WHEREIN_test)
runStep(t, mc, "WHEREEVAL", keys_WHEREEVAL_test) runStep(t, mc, "WHEREEVAL", keys_WHEREEVAL_test)
runStep(t, mc, "TYPE", keys_TYPE_test)
} }
func keys_BOUNDS_test(mc *mockServer) error { func keys_BOUNDS_test(mc *mockServer) error {
return mc.DoBatch( return mc.DoBatch(
Do("BOUNDS", "mykey").String("<nil>"), Do("BOUNDS", "mykey").Str("<nil>"),
Do("BOUNDS", "mykey").JSON().Error("key not found"), Do("BOUNDS", "mykey").JSON().Err("key not found"),
Do("SET", "mykey", "myid1", "POINT", 33, -115).OK(), Do("SET", "mykey", "myid1", "POINT", 33, -115).OK(),
Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"), Do("BOUNDS", "mykey").Str("[[-115 33] [-115 33]]"),
Do("BOUNDS", "mykey").JSON().String(`{"ok":true,"bounds":{"type":"Point","coordinates":[-115,33]}}`), Do("BOUNDS", "mykey").JSON().Str(`{"ok":true,"bounds":{"type":"Point","coordinates":[-115,33]}}`),
Do("SET", "mykey", "myid2", "POINT", 34, -112).OK(), Do("SET", "mykey", "myid2", "POINT", 34, -112).OK(),
Do("BOUNDS", "mykey").String("[[-115 33] [-112 34]]"), Do("BOUNDS", "mykey").Str("[[-115 33] [-112 34]]"),
Do("DEL", "mykey", "myid2").String("1"), Do("DEL", "mykey", "myid2").Str("1"),
Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"), Do("BOUNDS", "mykey").Str("[[-115 33] [-115 33]]"),
Do("SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`).OK(), Do("SET", "mykey", "myid3", "OBJECT", `{"type":"Point","coordinates":[-130,38,10]}`).OK(),
Do("SET", "mykey", "myid4", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`).OK(), Do("SET", "mykey", "myid4", "OBJECT", `{"type":"Point","coordinates":[-110,25,-8]}`).OK(),
Do("BOUNDS", "mykey").String("[[-130 25] [-110 38]]"), Do("BOUNDS", "mykey").Str("[[-130 25] [-110 38]]"),
Do("BOUNDS", "mykey", "hello").Error("wrong number of arguments for 'bounds' command"), Do("BOUNDS", "mykey", "hello").Err("wrong number of arguments for 'bounds' command"),
Do("BOUNDS", "nada").String("<nil>"), Do("BOUNDS", "nada").Str("<nil>"),
Do("BOUNDS", "nada").JSON().Error("key not found"), Do("BOUNDS", "nada").JSON().Err("key not found"),
Do("BOUNDS", "").String("<nil>"), Do("BOUNDS", "").Str("<nil>"),
Do("BOUNDS", "mykey").JSON().String(`{"ok":true,"bounds":{"type":"Polygon","coordinates":[[[-130,25],[-110,25],[-110,38],[-130,38],[-130,25]]]}}`), Do("BOUNDS", "mykey").JSON().Str(`{"ok":true,"bounds":{"type":"Polygon","coordinates":[[[-130,25],[-110,25],[-110,38],[-130,38],[-130,25]]]}}`),
) )
} }
func keys_DEL_test(mc *mockServer) error { func keys_DEL_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid", "POINT", 33, -115).OK(),
{"GET", "mykey", "myid", "POINT"}, {"[33 -115]"}, Do("GET", "mykey", "myid", "POINT").Str("[33 -115]"),
{"DEL", "mykey", "myid"}, {"1"}, Do("DEL", "mykey", "myid2", "ERRON404").Err("id not found"),
{"GET", "mykey", "myid"}, {nil}, Do("DEL", "mykey", "myid").Str("1"),
}) Do("DEL", "mykey", "myid").Str("0"),
Do("DEL", "mykey").Err("wrong number of arguments for 'del' command"),
Do("GET", "mykey", "myid").Str("<nil>"),
Do("DEL", "mykey", "myid", "ERRON404").Err("key not found"),
Do("DEL", "mykey", "myid", "invalid-arg").Err("invalid argument 'invalid-arg'"),
Do("SET", "mykey", "myid", "POINT", 33, -115).OK(),
Do("DEL", "mykey", "myid2", "ERRON404").JSON().Err("id not found"),
Do("DEL", "mykey", "myid").JSON().OK(),
Do("DEL", "mykey", "myid").JSON().OK(),
Do("DEL", "mykey", "myid", "ERRON404").JSON().Err("key not found"),
)
} }
func keys_DROP_test(mc *mockServer) error { func keys_DROP_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid1", "HASH", "9my5xp7"}, {"OK"}, {"SET", "mykey", "myid1", "HASH", "9my5xp7"}, {"OK"},
@ -138,14 +150,14 @@ func keys_FSET_test(mc *mockServer) error {
}) })
} }
func keys_GET_test(mc *mockServer) error { func keys_GET_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value").OK(),
{"GET", "mykey", "myid"}, {"value"}, Do("GET", "mykey", "myid").Str("value"),
{"SET", "mykey", "myid", "STRING", "value2"}, {"OK"}, Do("SET", "mykey", "myid", "STRING", "value2").OK(),
{"GET", "mykey", "myid"}, {"value2"}, Do("GET", "mykey", "myid").Str("value2"),
{"DEL", "mykey", "myid"}, {"1"}, Do("DEL", "mykey", "myid").Str("1"),
{"GET", "mykey", "myid"}, {nil}, Do("GET", "mykey", "myid").Str("<nil>"),
}) )
} }
func keys_KEYS_test(mc *mockServer) error { func keys_KEYS_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch([][]interface{}{
@ -314,24 +326,31 @@ func keys_FIELDS_test(mc *mockServer) error {
} }
func keys_PDEL_test(mc *mockServer) error { func keys_PDEL_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{ return mc.DoBatch(
{"SET", "mykey", "myid1a", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid1a", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid1b", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid1b", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid2a", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid2a", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid2b", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid2b", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid3a", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid3a", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid3b", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid3b", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid4a", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid4a", "POINT", 33, -115).OK(),
{"SET", "mykey", "myid4b", "POINT", 33, -115}, {"OK"}, Do("SET", "mykey", "myid4b", "POINT", 33, -115).OK(),
{"PDEL", "mykeyNA", "*"}, {0}, Do("PDEL", "mykey").Err("wrong number of arguments for 'pdel' command"),
{"PDEL", "mykey", "myid1a"}, {1}, Do("PDEL", "mykeyNA", "*").Str("0"),
{"PDEL", "mykey", "myid1a"}, {0}, Do("PDEL", "mykey", "myid1a").Str("1"),
{"PDEL", "mykey", "myid1*"}, {1}, Do("PDEL", "mykey", "myid1a").Str("0"),
{"PDEL", "mykey", "myid2*"}, {2}, Do("PDEL", "mykey", "myid1*").Str("1"),
{"PDEL", "mykey", "*b"}, {2}, Do("PDEL", "mykey", "myid2*").Str("2"),
{"PDEL", "mykey", "*"}, {2}, Do("PDEL", "mykey", "*b").Str("2"),
{"PDEL", "mykey", "*"}, {0}, Do("PDEL", "mykey", "*").Str("2"),
}) Do("PDEL", "mykey", "*").Str("0"),
Do("SET", "mykey", "myid1a", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid1b", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid2a", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid2b", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid3a", "POINT", 33, -115).OK(),
Do("PDEL", "mykey", "*").JSON().OK(),
)
} }
func keys_WHEREIN_test(mc *mockServer) error { func keys_WHEREIN_test(mc *mockServer) error {
@ -363,3 +382,14 @@ func keys_WHEREEVAL_test(mc *mockServer) error {
{"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1]) and FIELDS.a ~= tonumber(ARGV[2])", 2, 0.5, 3, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`}, {"WITHIN", "mykey", "WHEREEVAL", "return FIELDS.a > tonumber(ARGV[1]) and FIELDS.a ~= tonumber(ARGV[2])", 2, 0.5, 3, "BOUNDS", 32.8, -115.2, 33.2, -114.8}, {`[0 [[myid_a2 {"type":"Point","coordinates":[-115,32.99]} [a 2]] [myid_a1 {"type":"Point","coordinates":[-115,33]} [a 1]]]]`},
}) })
} }
func keys_TYPE_test(mc *mockServer) error {
return mc.DoBatch(
Do("SET", "mykey", "myid1", "POINT", 33, -115).OK(),
Do("TYPE", "mykey").Str("hash"),
Do("TYPE", "mykey", "hello").Err("wrong number of arguments for 'type' command"),
Do("TYPE", "mykey2").Str("none"),
Do("TYPE", "mykey2").JSON().Err("key not found"),
Do("TYPE", "mykey").JSON().Str(`{"ok":true,"type":"hash"}`),
)
}

View File

@ -27,7 +27,7 @@ func (cmd *IO) JSON() *IO {
cmd.json = true cmd.json = true
return cmd return cmd
} }
func (cmd *IO) String(s string) *IO { func (cmd *IO) Str(s string) *IO {
cmd.out = s cmd.out = s
return cmd return cmd
} }
@ -56,7 +56,7 @@ func (cmd *IO) OK() *IO {
}) })
} }
func (cmd *IO) Error(msg string) *IO { func (cmd *IO) Err(msg string) *IO {
return cmd.Custom(func(s string) error { return cmd.Custom(func(s string) error {
if cmd.json { if cmd.json {
if gjson.Get(s, "ok").Type != gjson.False { if gjson.Get(s, "ok").Type != gjson.False {