diff --git a/internal/server/crud.go b/internal/server/crud.go index a9a37877..65702cd3 100644 --- a/internal/server/crud.go +++ b/internal/server/crud.go @@ -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) { diff --git a/internal/server/expire.go b/internal/server/expire.go index cd67fd79..39abd412 100644 --- a/internal/server/expire.go +++ b/internal/server/expire.go @@ -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) } diff --git a/internal/server/scripts.go b/internal/server/scripts.go index e97d8066..1c3a5fc5 100644 --- a/internal/server/scripts.go +++ b/internal/server/scripts.go @@ -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": diff --git a/internal/server/server.go b/internal/server/server.go index 4cdc385f..b27ac477 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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": diff --git a/scripts/test.sh b/scripts/test.sh index a5997b74..36086708 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -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 diff --git a/tests/keys_test.go b/tests/keys_test.go index 29e4625d..ed5b3998 100644 --- a/tests/keys_test.go +++ b/tests/keys_test.go @@ -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(""), - Do("BOUNDS", "mykey").JSON().Error("key not found"), + Do("BOUNDS", "mykey").Str(""), + 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(""), - Do("BOUNDS", "nada").JSON().Error("key not found"), - Do("BOUNDS", "").String(""), - 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(""), + Do("BOUNDS", "nada").JSON().Err("key not found"), + Do("BOUNDS", "").Str(""), + 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(""), + 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(""), + ) } 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"}`), + ) +} diff --git a/tests/mock_io_test.go b/tests/mock_io_test.go index 3f07fd91..ba19ddc6 100644 --- a/tests/mock_io_test.go +++ b/tests/mock_io_test.go @@ -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 {