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
}
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()
vs := msg.Args[1:]
var ok bool
var key string
if _, key, ok = tokenval(vs); !ok || key == "" {
return NOMessage, errInvalidNumberOfArguments
// >> Args
args := msg.Args
if len(args) != 2 {
return retrerr(errInvalidNumberOfArguments)
}
key := args[1]
// >> Operation
col, _ := s.cols.Get(key)
if col == nil {
if msg.OutputType == RESP {
return resp.SimpleStringValue("none"), nil
}
return NOMessage, errKeyNotFound
return retrerr(errKeyNotFound)
}
// >> Response
typ := "hash"
switch msg.OutputType {
case JSON:
return resp.StringValue(`{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Since(start).String() + "\"}"), nil
case RESP:
return resp.SimpleStringValue(typ), nil
if msg.OutputType == JSON {
return resp.StringValue(`{"ok":true,"type":` + jsonString(typ) +
`,"elapsed":"` + time.Since(start).String() + "\"}"), nil
}
return NOMessage, nil
return resp.SimpleStringValue(typ), nil
}
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]
func (s *Server) cmdDel(msg *Message) (resp.Value, commandDetails, error) {
func (s *Server) cmdDEL(msg *Message) (resp.Value, commandDetails, error) {
start := time.Now()
// >> Args
@ -329,85 +334,75 @@ func (s *Server) cmdDel(msg *Message) (resp.Value, commandDetails, error) {
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()
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
col, _ := s.cols.Get(d.key)
// >> Args
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 {
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] == "" {
col.Scan(false, nil, msg.Deadline, iter)
} 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 i, dc := range d.children {
old := col.Delete(dc.obj.ID())
if old == nil {
d.children[i].command = "?"
atLeastOneNotDeleted = true
} else {
dc.obj = old
d.children[i] = dc
}
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
for _, id := range ids {
obj := col.Delete(id)
children = append(children, &commandDetails{
command: "del",
updated: true,
timestamp: now,
key: key,
obj: obj,
})
s.groupDisconnectObject(key, id)
}
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.children = children
d.key = key
d.pattern = pattern
d.updated = len(d.children) > 0
d.timestamp = now
d.parent = true
switch msg.OutputType {
case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Since(start).String() + "\"}")
res = resp.StringValue(`{"ok":true,"elapsed":"` +
time.Since(start).String() + "\"}")
case RESP:
total := len(d.children) - expired
if total < 0 {
total = 0
}
res = resp.IntegerValue(total)
res = resp.IntegerValue(len(d.children))
}
return
return res, d, nil
}
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
})
for _, msg := range msgs {
_, d, err := s.cmdDel(msg)
_, d, err := s.cmdDEL(msg)
if err != nil {
log.Fatal(err)
}

View File

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

View File

@ -1020,9 +1020,9 @@ func (s *Server) command(msg *Message, client *Client) (
case "fset":
res, d, err = s.cmdFSET(msg)
case "del":
res, d, err = s.cmdDel(msg)
res, d, err = s.cmdDEL(msg)
case "pdel":
res, d, err = s.cmdPdel(msg)
res, d, err = s.cmdPDEL(msg)
case "drop":
res, d, err = s.cmdDrop(msg)
case "flushdb":
@ -1106,7 +1106,7 @@ func (s *Server) command(msg *Message, client *Client) (
case "jdel":
res, d, err = s.cmdJdel(msg)
case "type":
res, err = s.cmdType(msg)
res, err = s.cmdTYPE(msg)
case "keys":
res, err = s.cmdKeys(msg)
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"
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, "WHEREIN", keys_WHEREIN_test)
runStep(t, mc, "WHEREEVAL", keys_WHEREEVAL_test)
runStep(t, mc, "TYPE", keys_TYPE_test)
}
func keys_BOUNDS_test(mc *mockServer) error {
return mc.DoBatch(
Do("BOUNDS", "mykey").String("<nil>"),
Do("BOUNDS", "mykey").JSON().Error("key not found"),
Do("BOUNDS", "mykey").Str("<nil>"),
Do("BOUNDS", "mykey").JSON().Err("key not found"),
Do("SET", "mykey", "myid1", "POINT", 33, -115).OK(),
Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"),
Do("BOUNDS", "mykey").JSON().String(`{"ok":true,"bounds":{"type":"Point","coordinates":[-115,33]}}`),
Do("BOUNDS", "mykey").Str("[[-115 33] [-115 33]]"),
Do("BOUNDS", "mykey").JSON().Str(`{"ok":true,"bounds":{"type":"Point","coordinates":[-115,33]}}`),
Do("SET", "mykey", "myid2", "POINT", 34, -112).OK(),
Do("BOUNDS", "mykey").String("[[-115 33] [-112 34]]"),
Do("DEL", "mykey", "myid2").String("1"),
Do("BOUNDS", "mykey").String("[[-115 33] [-115 33]]"),
Do("BOUNDS", "mykey").Str("[[-115 33] [-112 34]]"),
Do("DEL", "mykey", "myid2").Str("1"),
Do("BOUNDS", "mykey").Str("[[-115 33] [-115 33]]"),
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("BOUNDS", "mykey").String("[[-130 25] [-110 38]]"),
Do("BOUNDS", "mykey", "hello").Error("wrong number of arguments for 'bounds' command"),
Do("BOUNDS", "nada").String("<nil>"),
Do("BOUNDS", "nada").JSON().Error("key not found"),
Do("BOUNDS", "").String("<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").Str("[[-130 25] [-110 38]]"),
Do("BOUNDS", "mykey", "hello").Err("wrong number of arguments for 'bounds' command"),
Do("BOUNDS", "nada").Str("<nil>"),
Do("BOUNDS", "nada").JSON().Err("key not found"),
Do("BOUNDS", "").Str("<nil>"),
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 {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid", "POINT", 33, -115}, {"OK"},
{"GET", "mykey", "myid", "POINT"}, {"[33 -115]"},
{"DEL", "mykey", "myid"}, {"1"},
{"GET", "mykey", "myid"}, {nil},
})
return mc.DoBatch(
Do("SET", "mykey", "myid", "POINT", 33, -115).OK(),
Do("GET", "mykey", "myid", "POINT").Str("[33 -115]"),
Do("DEL", "mykey", "myid2", "ERRON404").Err("id not found"),
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 {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid1", "HASH", "9my5xp7"}, {"OK"},
@ -138,14 +150,14 @@ func keys_FSET_test(mc *mockServer) error {
})
}
func keys_GET_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid", "STRING", "value"}, {"OK"},
{"GET", "mykey", "myid"}, {"value"},
{"SET", "mykey", "myid", "STRING", "value2"}, {"OK"},
{"GET", "mykey", "myid"}, {"value2"},
{"DEL", "mykey", "myid"}, {"1"},
{"GET", "mykey", "myid"}, {nil},
})
return mc.DoBatch(
Do("SET", "mykey", "myid", "STRING", "value").OK(),
Do("GET", "mykey", "myid").Str("value"),
Do("SET", "mykey", "myid", "STRING", "value2").OK(),
Do("GET", "mykey", "myid").Str("value2"),
Do("DEL", "mykey", "myid").Str("1"),
Do("GET", "mykey", "myid").Str("<nil>"),
)
}
func keys_KEYS_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
@ -314,24 +326,31 @@ func keys_FIELDS_test(mc *mockServer) error {
}
func keys_PDEL_test(mc *mockServer) error {
return mc.DoBatch([][]interface{}{
{"SET", "mykey", "myid1a", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid1b", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid2a", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid2b", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid3a", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid3b", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid4a", "POINT", 33, -115}, {"OK"},
{"SET", "mykey", "myid4b", "POINT", 33, -115}, {"OK"},
{"PDEL", "mykeyNA", "*"}, {0},
{"PDEL", "mykey", "myid1a"}, {1},
{"PDEL", "mykey", "myid1a"}, {0},
{"PDEL", "mykey", "myid1*"}, {1},
{"PDEL", "mykey", "myid2*"}, {2},
{"PDEL", "mykey", "*b"}, {2},
{"PDEL", "mykey", "*"}, {2},
{"PDEL", "mykey", "*"}, {0},
})
return mc.DoBatch(
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("SET", "mykey", "myid3b", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid4a", "POINT", 33, -115).OK(),
Do("SET", "mykey", "myid4b", "POINT", 33, -115).OK(),
Do("PDEL", "mykey").Err("wrong number of arguments for 'pdel' command"),
Do("PDEL", "mykeyNA", "*").Str("0"),
Do("PDEL", "mykey", "myid1a").Str("1"),
Do("PDEL", "mykey", "myid1a").Str("0"),
Do("PDEL", "mykey", "myid1*").Str("1"),
Do("PDEL", "mykey", "myid2*").Str("2"),
Do("PDEL", "mykey", "*b").Str("2"),
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 {
@ -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]]]]`},
})
}
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
return cmd
}
func (cmd *IO) String(s string) *IO {
func (cmd *IO) Str(s string) *IO {
cmd.out = s
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 {
if cmd.json {
if gjson.Get(s, "ok").Type != gjson.False {