From f85da1db9194638995108696b8980ba994cef3f8 Mon Sep 17 00:00:00 2001 From: siddontang Date: Thu, 17 Jul 2014 15:19:46 +0800 Subject: [PATCH 01/51] add base http framework --- bootstrap.sh | 1 + etc/ledis.json | 1 + server/app.go | 44 +++++++++++++++++++++++++++++++++++++------- server/config.go | 2 ++ server/httpd.go | 22 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 server/httpd.go diff --git a/bootstrap.sh b/bootstrap.sh index 17eeb35..dbbf060 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -3,4 +3,5 @@ . ./dev.sh go get -u github.com/siddontang/go-log/log +go get -u github.com/siddontang/go-websocket/websocket go get -u github.com/siddontang/go-snappy/snappy \ No newline at end of file diff --git a/etc/ledis.json b/etc/ledis.json index 0d93e88..46b0b87 100644 --- a/etc/ledis.json +++ b/etc/ledis.json @@ -1,5 +1,6 @@ { "addr": "127.0.0.1:6380", + "http_addr": "127.0.0.1:11181", "data_dir": "/tmp/ledis_server", "db": { "data_db" : { diff --git a/server/app.go b/server/app.go index 9f155c1..fb3f45a 100644 --- a/server/app.go +++ b/server/app.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/siddontang/ledisdb/ledis" "net" + "net/http" "path" "strings" ) @@ -11,7 +12,8 @@ import ( type App struct { cfg *Config - listener net.Listener + listener net.Listener + httpListener net.Listener ldb *ledis.Ledis @@ -25,6 +27,14 @@ type App struct { m *master } +func netType(s string) string { + if strings.Contains(s, "/") { + return "unix" + } else { + return "tcp" + } +} + func NewApp(cfg *Config) (*App, error) { if len(cfg.DataDir) == 0 { return nil, fmt.Errorf("must set data_dir first") @@ -44,14 +54,14 @@ func NewApp(cfg *Config) (*App, error) { var err error - if strings.Contains(cfg.Addr, "/") { - app.listener, err = net.Listen("unix", cfg.Addr) - } else { - app.listener, err = net.Listen("tcp", cfg.Addr) + if app.listener, err = net.Listen(netType(cfg.Addr), cfg.Addr); err != nil { + return nil, err } - if err != nil { - return nil, err + if len(cfg.HttpAddr) > 0 { + if app.httpListener, err = net.Listen(netType(cfg.HttpAddr), cfg.HttpAddr); err != nil { + return nil, err + } } if len(cfg.AccessLog) > 0 { @@ -86,6 +96,10 @@ func (app *App) Close() { app.listener.Close() + if app.httpListener != nil { + app.httpListener.Close() + } + app.m.Close() if app.access != nil { @@ -100,6 +114,8 @@ func (app *App) Run() { app.slaveof(app.cfg.SlaveOf) } + go app.httpServe() + for !app.closed { conn, err := app.listener.Accept() if err != nil { @@ -110,6 +126,20 @@ func (app *App) Run() { } } +func (app *App) httpServe() { + if app.httpListener == nil { + return + } + + mux := http.NewServeMux() + + mux.Handle("/ws", &wsHandler{app}) + mux.Handle("/", &cmdHandler{app}) + + svr := http.Server{Handler: mux} + svr.Serve(app.httpListener) +} + func (app *App) Ledis() *ledis.Ledis { return app.ldb } diff --git a/server/config.go b/server/config.go index 89aba2c..3c030be 100644 --- a/server/config.go +++ b/server/config.go @@ -9,6 +9,8 @@ import ( type Config struct { Addr string `json:"addr"` + HttpAddr string `json:"http_addr"` + DataDir string `json:"data_dir"` //if you not set db path, use data_dir diff --git a/server/httpd.go b/server/httpd.go new file mode 100644 index 0000000..29cf14a --- /dev/null +++ b/server/httpd.go @@ -0,0 +1,22 @@ +package server + +import ( + "net/http" + //"github.com/siddontang/go-websocket/websocket" +) + +type cmdHandler struct { + app *App +} + +func (h *cmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("cmd handler")) +} + +type wsHandler struct { + app *App +} + +func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ws handler")) +} From 1513d055f5bb2ca9f96fdde30297980ae12d5242 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 22 Jul 2014 11:31:56 +0800 Subject: [PATCH 02/51] http api of bit cmds --- server/app.go | 5 +- server/http/base.go | 26 +++++ server/http/cmd_bit.go | 228 +++++++++++++++++++++++++++++++++++++++++ server/http/handler.go | 101 ++++++++++++++++++ server/httpd.go | 22 ---- 5 files changed, 358 insertions(+), 24 deletions(-) create mode 100644 server/http/base.go create mode 100644 server/http/cmd_bit.go create mode 100644 server/http/handler.go delete mode 100644 server/httpd.go diff --git a/server/app.go b/server/app.go index fb3f45a..7fbcb65 100644 --- a/server/app.go +++ b/server/app.go @@ -3,6 +3,7 @@ package server import ( "fmt" "github.com/siddontang/ledisdb/ledis" + . "github.com/siddontang/ledisdb/server/http" "net" "net/http" "path" @@ -133,8 +134,8 @@ func (app *App) httpServe() { mux := http.NewServeMux() - mux.Handle("/ws", &wsHandler{app}) - mux.Handle("/", &cmdHandler{app}) + mux.Handle("/ws", &WsHandler{app.Ledis()}) + mux.Handle("/", &CmdHandler{app.Ledis()}) svr := http.Server{Handler: mux} svr.Serve(app.httpListener) diff --git a/server/http/base.go b/server/http/base.go new file mode 100644 index 0000000..4a05cb4 --- /dev/null +++ b/server/http/base.go @@ -0,0 +1,26 @@ +package http + +import ( + "errors" + "fmt" + "github.com/siddontang/ledisdb/ledis" + "strings" +) + +const ERR_ARGUMENT_FORMAT = "ERR wrong number of arguments for '%s' command" + +var ErrValue = errors.New("ERR value is not an integer or out of range") + +type commondFunc func(*ledis.DB, ...string) (interface{}, error) + +var regCmds = map[string]commondFunc{} + +func register(name string, f commondFunc) { + if _, ok := regCmds[strings.ToLower(name)]; ok { + panic(fmt.Sprintf("%s has been registered", name)) + } + regCmds[name] = f +} +func lookup(name string) commondFunc { + return regCmds[strings.ToLower(name)] +} diff --git a/server/http/cmd_bit.go b/server/http/cmd_bit.go new file mode 100644 index 0000000..999b3a4 --- /dev/null +++ b/server/http/cmd_bit.go @@ -0,0 +1,228 @@ +package http + +import ( + "fmt" + "github.com/siddontang/ledisdb/ledis" + "strconv" + "strings" +) + +func bgetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bget") + } + if v, err := db.BGet(ledis.Slice(args[0])); err != nil { + return nil, err + } else { + return v, nil + } +} + +func bdeleteCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bdelete") + } + if n, err := db.BDelete(ledis.Slice(args[0])); err != nil { + return nil, err + } else { + return n, err + } +} + +func bsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bsetbit") + } + key := ledis.Slice(args[0]) + offset, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return nil, ErrValue + } + val, err := strconv.ParseUint(args[2], 10, 8) + if ori, err := db.BSetBit(key, int32(offset), uint8(val)); err != nil { + return nil, err + + } else { + return ori, nil + } +} + +func bgetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bgetbit") + } + key := ledis.Slice(args[0]) + offset, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return nil, ErrValue + } + + if v, err := db.BGetBit(key, int32(offset)); err != nil { + return nil, err + } else { + return v, nil + } + + return nil, nil +} + +func bmsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") + } + key := ledis.Slice(args[0]) + if len(args[1:])%2 != 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") + } else { + args = args[1:] + } + pairs := make([]ledis.BitPair, len(args)/2) + for i := 0; i < len(pairs); i++ { + offset, err := strconv.ParseInt(args[i*2], 10, 32) + if err != nil { + return nil, err + } + val, err := strconv.ParseUint(args[i*2+1], 10, 8) + if err != nil { + return nil, err + } + pairs[i].Pos = int32(offset) + pairs[i].Val = uint8(val) + } + if place, err := db.BMSetBit(key, pairs...); err != nil { + return nil, err + } else { + return place, nil + } +} + +func bcountCommand(db *ledis.DB, args ...string) (interface{}, error) { + argCnt := len(args) + if argCnt > 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bcount") + } + + var err error + var start, end int64 = 0, -1 + if argCnt > 1 { + if start, err = strconv.ParseInt(args[1], 10, 32); err != nil { + return nil, err + } + } + if argCnt > 2 { + if end, err = strconv.ParseInt(args[1], 10, 32); err != nil { + return nil, err + } + } + key := ledis.Slice(args[0]) + if cnt, err := db.BCount(key, int32(start), int32(end)); err != nil { + return nil, err + } else { + return cnt, nil + } +} + +func boptCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bopt") + } + opDesc := strings.ToLower(args[0]) + dstKey := ledis.Slice(args[1]) + + var srcKeys = [][]byte{} + if len(args) >= 3 { + srcKeys = make([][]byte, len(args[2:])) + for i, arg := range args[2:] { + srcKeys[i] = ledis.Slice(arg) + } + } + + var op uint8 + switch opDesc { + case "and": + op = ledis.OPand + case "or": + op = ledis.OPor + case "xor": + op = ledis.OPxor + case "not": + op = ledis.OPnot + default: + return nil, fmt.Errorf("ERR invalid argument '%s' for 'bopt' command", opDesc) + } + if blen, err := db.BOperation(op, dstKey, srcKeys...); err != nil { + return nil, err + } else { + return blen, nil + } +} + +func bexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bexpire") + } + duration, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, err + } + key := ledis.Slice(args[0]) + if v, err := db.BExpire(key, duration); err != nil { + return nil, err + } else { + return v, err + } +} + +func bexpireatCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bexpireat") + } + when, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, err + } + key := ledis.Slice(args[0]) + if v, err := db.BExpireAt(key, when); err != nil { + return nil, err + } else { + return v, nil + } +} + +func bttlCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bttl") + } + key := ledis.Slice(args[0]) + if v, err := db.BTTL(key); err != nil { + return nil, err + } else { + return v, err + } +} + +func bpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bpersist") + } + key := ledis.Slice(args[0]) + if n, err := db.BPersist(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func init() { + register("bget", bgetCommand) + register("bdelete", bdeleteCommand) + register("bsetbit", bsetbitCommand) + register("bgetbit", bgetbitCommand) + register("bmsetbit", bmsetbitCommand) + register("bcount", bcountCommand) + register("bopt", boptCommand) + register("bexpire", bexpireCommand) + register("bexpireat", bexpireatCommand) + register("bttl", bttlCommand) + register("bpersist", bpersistCommand) +} diff --git a/server/http/handler.go b/server/http/handler.go new file mode 100644 index 0000000..34aa24f --- /dev/null +++ b/server/http/handler.go @@ -0,0 +1,101 @@ +package http + +import ( + "net/http" + //"github.com/siddontang/go-websocket/websocket" + "encoding/json" + "fmt" + "github.com/siddontang/go-log/log" + "github.com/siddontang/ledisdb/ledis" + "strconv" + "strings" +) + +type CmdHandler struct { + Ldb *ledis.Ledis +} + +func (h *CmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + w.WriteHeader(http.StatusForbidden) + return + } + idx, cmd, args := h.parseReqPath(r.URL.Path) + cmdFunc := lookup(cmd) + if cmdFunc == nil { + h.cmdNotFound(cmd, w) + return + } + var db *ledis.DB + var err error + if db, err = h.Ldb.Select(idx); err != nil { + h.serverError(cmd, err, w) + return + } + result, err := cmdFunc(db, args...) + if err != nil { + h.serverError(cmd, err, w) + return + } + h.write(cmd, result, w) +} + +func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []string) { + substrings := strings.Split(strings.TrimLeft(path, "/"), "/") + if len(substrings) == 1 { + return 0, substrings[0], substrings[1:] + } + db, err := strconv.Atoi(substrings[0]) + if err != nil { + // db = 0 + cmd = substrings[0] + args = substrings[1:] + } else { + cmd = substrings[1] + args = substrings[2:] + } + return +} +func (h *CmdHandler) cmdNotFound(cmd string, w http.ResponseWriter) { + result := [2]interface{}{ + false, + fmt.Sprintf("ERR unknown command '%s'", cmd), + } + h.write(cmd, result, w) +} + +func (h *CmdHandler) write(cmd string, result interface{}, w http.ResponseWriter) { + m := map[string]interface{}{ + cmd: result, + } + + buf, err := json.Marshal(&m) + if err != nil { + log.Error(err.Error()) + return + } + + w.Header().Set("Content-type", "application/json; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(len(buf))) + + _, err = w.Write(buf) + if err != nil { + log.Error(err.Error()) + } +} + +func (h *CmdHandler) serverError(cmd string, err error, w http.ResponseWriter) { + result := [2]interface{}{ + false, + fmt.Sprintf("ERR %s", err.Error()), + } + h.write(cmd, result, w) +} + +type WsHandler struct { + Ldb *ledis.Ledis +} + +func (h *WsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ws handler")) +} diff --git a/server/httpd.go b/server/httpd.go deleted file mode 100644 index 29cf14a..0000000 --- a/server/httpd.go +++ /dev/null @@ -1,22 +0,0 @@ -package server - -import ( - "net/http" - //"github.com/siddontang/go-websocket/websocket" -) - -type cmdHandler struct { - app *App -} - -func (h *cmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("cmd handler")) -} - -type wsHandler struct { - app *App -} - -func (h *wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("ws handler")) -} From 43905129b853b69e8cb2b8f9fc3dab894a53396c Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 22 Jul 2014 15:43:22 +0800 Subject: [PATCH 03/51] use '[]byte()' to convert string to []byte --- server/http/cmd_bit.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/http/cmd_bit.go b/server/http/cmd_bit.go index 999b3a4..e5d9965 100644 --- a/server/http/cmd_bit.go +++ b/server/http/cmd_bit.go @@ -11,7 +11,7 @@ func bgetCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 1 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bget") } - if v, err := db.BGet(ledis.Slice(args[0])); err != nil { + if v, err := db.BGet([]byte(args[0])); err != nil { return nil, err } else { return v, nil @@ -22,7 +22,7 @@ func bdeleteCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 1 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bdelete") } - if n, err := db.BDelete(ledis.Slice(args[0])); err != nil { + if n, err := db.BDelete([]byte(args[0])); err != nil { return nil, err } else { return n, err @@ -33,7 +33,7 @@ func bsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 3 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bsetbit") } - key := ledis.Slice(args[0]) + key := []byte(args[0]) offset, err := strconv.ParseInt(args[1], 10, 32) if err != nil { return nil, ErrValue @@ -51,7 +51,7 @@ func bgetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 2 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bgetbit") } - key := ledis.Slice(args[0]) + key := []byte(args[0]) offset, err := strconv.ParseInt(args[1], 10, 32) if err != nil { return nil, ErrValue @@ -70,7 +70,7 @@ func bmsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) < 3 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if len(args[1:])%2 != 0 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") } else { @@ -114,7 +114,7 @@ func bcountCommand(db *ledis.DB, args ...string) (interface{}, error) { return nil, err } } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if cnt, err := db.BCount(key, int32(start), int32(end)); err != nil { return nil, err } else { @@ -127,13 +127,13 @@ func boptCommand(db *ledis.DB, args ...string) (interface{}, error) { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bopt") } opDesc := strings.ToLower(args[0]) - dstKey := ledis.Slice(args[1]) + dstKey := []byte(args[1]) var srcKeys = [][]byte{} if len(args) >= 3 { srcKeys = make([][]byte, len(args[2:])) for i, arg := range args[2:] { - srcKeys[i] = ledis.Slice(arg) + srcKeys[i] = []byte(arg) } } @@ -165,7 +165,7 @@ func bexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { if err != nil { return nil, err } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if v, err := db.BExpire(key, duration); err != nil { return nil, err } else { @@ -181,7 +181,7 @@ func bexpireatCommand(db *ledis.DB, args ...string) (interface{}, error) { if err != nil { return nil, err } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if v, err := db.BExpireAt(key, when); err != nil { return nil, err } else { @@ -193,7 +193,7 @@ func bttlCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 1 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bttl") } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if v, err := db.BTTL(key); err != nil { return nil, err } else { @@ -205,7 +205,7 @@ func bpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { if len(args) != 1 { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bpersist") } - key := ledis.Slice(args[0]) + key := []byte(args[0]) if n, err := db.BPersist(key); err != nil { return nil, err } else { From 1dd7d22198133994536baa40d40a948708c56484 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Wed, 23 Jul 2014 10:59:37 +0800 Subject: [PATCH 04/51] add unit test --- server/http/cmd_bit.go | 2 +- server/http/cmd_bit_test.go | 93 +++++++++++++++++++++++++++++++++++++ server/http/handler.go | 5 +- 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 server/http/cmd_bit_test.go diff --git a/server/http/cmd_bit.go b/server/http/cmd_bit.go index e5d9965..538b3a9 100644 --- a/server/http/cmd_bit.go +++ b/server/http/cmd_bit.go @@ -14,7 +14,7 @@ func bgetCommand(db *ledis.DB, args ...string) (interface{}, error) { if v, err := db.BGet([]byte(args[0])); err != nil { return nil, err } else { - return v, nil + return ledis.String(v), nil } } diff --git a/server/http/cmd_bit_test.go b/server/http/cmd_bit_test.go new file mode 100644 index 0000000..21844db --- /dev/null +++ b/server/http/cmd_bit_test.go @@ -0,0 +1,93 @@ +package http + +import ( + // "github.com/siddontang/ledisdb/ledis" + "testing" +) + +func TestBgetCommand(t *testing.T) { + db := getTestDB() + db.BSetBit([]byte("test_bget"), 0, 1) + db.BSetBit([]byte("test_bget"), 1, 1) + db.BSetBit([]byte("test_bget"), 2, 1) + + _, err := bgetCommand(db, "test_bget", "a", "b", "c") + if err == nil || err.Error() != "ERR wrong number of arguments for 'bget' command" { + t.Fatal("invalid err %v", err) + } + + r, err := bgetCommand(db, "test_bget") + if err != nil { + t.Fatal(err.Error()) + } + str := r.(string) + if str != "\x07" { + t.Fatal("wrong result of 'bget': %v", []byte(str)) + } +} + +func TestBDeleteCommand(t *testing.T) { + db := getTestDB() + + _, err := bdeleteCommand(db, "test_bdelete", "a", "b", "c") + if err == nil || err.Error() != "ERR wrong number of arguments for 'bdelete' command" { + t.Fatalf("invalid err %v", err) + } + + db.BSetBit([]byte("test_bdelete"), 0, 1) + db.BSetBit([]byte("test_bdelete"), 1, 1) + db.BSetBit([]byte("test_bdelete"), 2, 1) + n, err := bdeleteCommand(db, "test_bdelete") + if err != nil { + t.Fatal(err.Error()) + } + if n.(int64) != 1 { + t.Fatalf("wrong result: %v", n) + } + + n, err = bdeleteCommand(db, "test_bdelete_not_exit") + if err != nil { + t.Fatal(err.Error()) + } + if n.(int64) != 0 { + t.Fatalf("wrong result: %v", n) + } +} + +func TestBSetbitCommand(t *testing.T) { + db := getTestDB() + _, err := bsetbitCommand(db, "test_bsetbit", "a", "b", "c") + if err == nil || err.Error() != "ERR wrong number of arguments for 'bsetbit' command" { + t.Fatalf("invalid err %v", err) + } + n, err := bsetbitCommand(db, "test_bsetbit", "1", "1") + if err != nil { + t.Fatal(err.Error()) + } + if n.(uint8) != 0 { + t.Fatal("wrong result: %v", n) + } + n, err = db.BGetBit([]byte("test_bsetbit"), 1) + if err != nil { + t.Fatal(err.Error()) + } + if n.(uint8) != 1 { + t.Fatalf("wrong result: %v", n) + } +} + +func TestBMsetbitCommand(t *testing.T) { + db := getTestDB() + _, err := bmsetbitCommand(db, "test_bmsetbit", "a", "b", "c") + + if err == nil || err.Error() != "ERR wrong number of arguments for 'bmsetbit' command" { + t.Fatalf("invalid err %v", err) + } + n, err := bmsetbitCommand(db, "test_bmsetbit", "1", "1", "3", "1") + if err != nil { + t.Fatal(err.Error()) + } + if n.(int64) != 2 { + t.Fatalf("wrong result: %v", n) + } +} diff --git a/server/http/handler.go b/server/http/handler.go index 34aa24f..d395534 100644 --- a/server/http/handler.go +++ b/server/http/handler.go @@ -41,13 +41,16 @@ func (h *CmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []string) { + /* + the proper format of `path` is /cmd/arg1/arg2/../argN or /db/cmd/arg1/arg2/../argN + if `path` is the first kind, `db` will be 0 + */ substrings := strings.Split(strings.TrimLeft(path, "/"), "/") if len(substrings) == 1 { return 0, substrings[0], substrings[1:] } db, err := strconv.Atoi(substrings[0]) if err != nil { - // db = 0 cmd = substrings[0] args = substrings[1:] } else { From f647c3d815cbce7be295158c84408aab30936f19 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Wed, 23 Jul 2014 11:02:10 +0800 Subject: [PATCH 05/51] add file --- server/http/base_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 server/http/base_test.go diff --git a/server/http/base_test.go b/server/http/base_test.go new file mode 100644 index 0000000..1f07b31 --- /dev/null +++ b/server/http/base_test.go @@ -0,0 +1,40 @@ +package http + +import ( + "github.com/siddontang/ledisdb/ledis" + "os" + "sync" +) + +var once sync.Once +var ldb *ledis.Ledis + +func getTestDB() *ledis.DB { + f := func() { + var err error + if _, err := os.Stat("/tmp/test_http_api_db"); err == nil { + if err := os.RemoveAll("/tmp/test_http_api_db"); err != nil { + panic(err) + } + } else if err != os.ErrNotExist { + panic(err) + } + var cfg ledis.Config + cfg.DataDir = "/tmp/test_http_api_db" + cfg.DataDB.BlockSize = 32768 + cfg.DataDB.WriteBufferSize = 20971520 + cfg.DataDB.CacheSize = 20971520 + cfg.DataDB.Compression = true + + ldb, err = ledis.Open(&cfg) + if err != nil { + panic(err) + } + } + once.Do(f) + db, err := ldb.Select(0) + if err != nil { + panic(err) + } + return db +} From a6a760880d9a338352fce0538994797afc01bd6f Mon Sep 17 00:00:00 2001 From: wenyekui Date: Thu, 24 Jul 2014 11:29:13 +0800 Subject: [PATCH 06/51] complete apis for the rest of cmds --- server/http/base.go | 10 +- server/http/cmd_hash.go | 306 +++++++++++++++++++++++ server/http/cmd_kv.go | 278 +++++++++++++++++++++ server/http/cmd_list.go | 248 +++++++++++++++++++ server/http/cmd_zset.go | 520 ++++++++++++++++++++++++++++++++++++++++ server/http/handler.go | 5 +- 6 files changed, 1363 insertions(+), 4 deletions(-) create mode 100644 server/http/cmd_hash.go create mode 100644 server/http/cmd_kv.go create mode 100644 server/http/cmd_list.go create mode 100644 server/http/cmd_zset.go diff --git a/server/http/base.go b/server/http/base.go index 4a05cb4..6b1781d 100644 --- a/server/http/base.go +++ b/server/http/base.go @@ -7,9 +7,15 @@ import ( "strings" ) -const ERR_ARGUMENT_FORMAT = "ERR wrong number of arguments for '%s' command" +const ( + ERR_ARGUMENT_FORMAT = "ERR wrong number of arguments for '%s' command" + MSG_OK = "OK" +) -var ErrValue = errors.New("ERR value is not an integer or out of range") +var ( + ErrValue = errors.New("ERR value is not an integer or out of range") + ErrSyntax = errors.New("ERR syntax error") +) type commondFunc func(*ledis.DB, ...string) (interface{}, error) diff --git a/server/http/cmd_hash.go b/server/http/cmd_hash.go new file mode 100644 index 0000000..cf0955b --- /dev/null +++ b/server/http/cmd_hash.go @@ -0,0 +1,306 @@ +package http + +import ( + "fmt" + "github.com/siddontang/ledisdb/ledis" + "strconv" +) + +func hsetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hset") + } + + key := []byte(args[0]) + field := []byte(args[1]) + value := []byte(args[2]) + if n, err := db.HSet(key, field, value); err != nil { + return nil, err + } else { + return n, err + } +} + +func hgetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hget") + } + + key := []byte(args[0]) + field := []byte(args[1]) + + if v, err := db.HGet(key, field); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func hexistsCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexists") + } + key := []byte(args[0]) + field := []byte(args[1]) + + var n int64 = 1 + if v, err := db.HGet(key, field); err != nil { + return nil, err + } else { + if v == nil { + n = 0 + } + return n, nil + } +} + +func hdelCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hdel") + } + key := []byte(args[0]) + fields := make([][]byte, len(args[1:])) + for i, arg := range args[1:] { + fields[i] = []byte(arg) + } + + if n, err := db.HDel(key, fields...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func hlenCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hlen") + } + key := []byte(args[0]) + if n, err := db.HLen(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func hincrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hincrby") + } + key := []byte(args[0]) + field := []byte(args[1]) + delta, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return nil, ErrValue + } + + var n int64 + if n, err = db.HIncrBy(key, field, delta); err != nil { + return nil, err + } else { + return n, nil + } +} + +func hmsetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmset") + } + + if len(args[1:])%2 != 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmset") + } + key := []byte(args[0]) + args = args[1:] + kvs := make([]ledis.FVPair, len(args)/2) + for i := 0; i < len(kvs); i++ { + kvs[i].Field = []byte(args[2*i]) + kvs[i].Value = []byte(args[2*i+1]) + } + if err := db.HMset(key, kvs...); err != nil { + return nil, err + } else { + return []interface{}{true, MSG_OK}, nil + } +} + +func hmgetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmget") + } + key := []byte(args[0]) + fields := make([][]byte, len(args[1:])) + for i, arg := range args[1:] { + fields[i] = []byte(arg) + } + if vals, err := db.HMget(key, fields...); err != nil { + return nil, err + } else { + arr := make([]interface{}, len(vals)) + for i, v := range vals { + if v == nil { + arr[i] = nil + } else { + arr[i] = ledis.String(v) + } + } + return arr, nil + } +} + +func hgetallCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hgetall") + } + key := []byte(args[0]) + if fvs, err := db.HGetAll(key); err != nil { + return nil, err + } else { + var m = make(map[string]string) + for _, fv := range fvs { + m[ledis.String(fv.Field)] = ledis.String(fv.Value) + } + return m, nil + } +} + +func hkeysCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hkeys") + } + key := []byte(args[0]) + if v, err := db.HKeys(key); err != nil { + return nil, err + } else { + return v, nil + } +} + +func hvalsCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hvals") + } + key := []byte(args[0]) + if vals, err := db.HValues(key); err != nil { + return nil, err + } else { + var arr = make([]string, len(vals)) + for i, v := range vals { + arr[i] = ledis.String(v) + } + return arr, nil + } +} + +func hclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hclear") + } + key := []byte(args[0]) + if n, err := db.HClear(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func hmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmclear") + } + keys := make([][]byte, len(args)) + for i, arg := range args { + keys[i] = []byte(arg) + } + + if n, err := db.HMclear(keys...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func hexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexpire") + } + key := []byte(args[0]) + duration, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + if v, err := db.HExpire(key, duration); err != nil { + return nil, err + } else { + return v, nil + } +} + +func hexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexpireat") + } + key := []byte(args[0]) + + when, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + if v, err := db.HExpireAt(key, when); err != nil { + return nil, err + } else { + return v, nil + } +} + +func httlCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "httl") + } + + key := []byte(args[0]) + if v, err := db.HTTL(key); err != nil { + return nil, err + } else { + return v, nil + } +} + +func hpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hpersist") + } + key := []byte(args[0]) + if n, err := db.HPersist(key); err != nil { + return nil, err + } else { + return n, err + } +} + +func init() { + register("hdel", hdelCommand) + register("hexists", hexistsCommand) + register("hget", hgetCommand) + register("hgetall", hgetallCommand) + register("hincrby", hincrbyCommand) + register("hkeys", hkeysCommand) + register("hlen", hlenCommand) + register("hmget", hmgetCommand) + register("hmset", hmsetCommand) + register("hset", hsetCommand) + register("hvals", hvalsCommand) + + //ledisdb special command + + register("hclear", hclearCommand) + register("hmclear", hmclearCommand) + register("hexpire", hexpireCommand) + register("hexpireat", hexpireAtCommand) + register("httl", httlCommand) + register("hpersist", hpersistCommand) +} diff --git a/server/http/cmd_kv.go b/server/http/cmd_kv.go new file mode 100644 index 0000000..dbd1116 --- /dev/null +++ b/server/http/cmd_kv.go @@ -0,0 +1,278 @@ +package http + +import ( + "fmt" + "github.com/siddontang/ledisdb/ledis" + "strconv" +) + +func getCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "get") + } + key := []byte(args[0]) + if v, err := db.Get(key); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func setCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "set") + } + + key := []byte(args[0]) + val := []byte(args[1]) + if err := db.Set(key, val); err != nil { + return nil, err + } else { + return []interface{}{true, MSG_OK}, nil + } + +} + +func getsetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "getset") + } + + key := []byte(args[0]) + val := []byte(args[1]) + if v, err := db.GetSet(key, val); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func setnxCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "setnx") + } + + key := []byte(args[0]) + val := []byte(args[1]) + if n, err := db.SetNX(key, val); err != nil { + return nil, err + } else { + return n, nil + } +} + +func existsCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "exists") + } + + if n, err := db.Exists([]byte(args[0])); err != nil { + return nil, err + } else { + return n, nil + } +} + +func incrCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "incr") + } + + key := []byte(args[0]) + if n, err := db.Incr(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func decrCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "decr") + } + + key := []byte(args[0]) + if n, err := db.Decr(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func incrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "incrby") + } + + delta, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + + if n, err := db.IncryBy(key, delta); err != nil { + return nil, err + } else { + return n, nil + } +} + +func decrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "decrby") + } + + delta, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + + if n, err := db.DecrBy(key, delta); err != nil { + return nil, err + } else { + return n, nil + } +} + +func delCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) == 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "del") + } + + keys := make([][]byte, len(args)) + if n, err := db.Del(keys...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func msetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) == 0 || len(args)%2 != 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "mset") + } + + kvs := make([]ledis.KVPair, len(args)/2) + for i := 0; i < len(kvs); i++ { + kvs[i].Key = []byte(args[2*i]) + kvs[i].Value = []byte(args[2*i+1]) + } + + if err := db.MSet(kvs...); err != nil { + return nil, err + } else { + return []interface{}{true, MSG_OK}, nil + } +} + +func mgetCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) == 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "mget") + } + + keys := make([][]byte, len(args)) + for i, arg := range args { + keys[i] = []byte(arg) + } + if vals, err := db.MGet(keys...); err != nil { + return nil, err + } else { + arr := make([]interface{}, len(vals)) + for i, v := range vals { + if v == nil { + arr[i] = nil + } else { + arr[i] = ledis.String(v) + } + } + return arr, nil + } +} + +func expireCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "expire") + } + + duration, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + key := []byte(args[0]) + if v, err := db.Expire(key, duration); err != nil { + return nil, err + } else { + return v, nil + } +} + +func expireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "expireat") + } + + when, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + key := []byte(args[0]) + if v, err := db.ExpireAt(key, when); err != nil { + return nil, err + } else { + return v, nil + } +} + +func ttlCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "ttl") + } + key := []byte(args[0]) + + if v, err := db.TTL(key); err != nil { + return nil, err + } else { + return v, nil + } +} + +func persistCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "persist") + } + key := []byte(args[0]) + + if n, err := db.Persist(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func init() { + register("decr", decrCommand) + register("decrby", decrbyCommand) + register("del", delCommand) + register("exists", existsCommand) + register("get", getCommand) + register("getset", getsetCommand) + register("incr", incrCommand) + register("incrby", incrbyCommand) + register("mget", mgetCommand) + register("mset", msetCommand) + register("set", setCommand) + register("setnx", setnxCommand) + register("expire", expireCommand) + register("expireat", expireAtCommand) + register("ttl", ttlCommand) + register("persist", persistCommand) +} diff --git a/server/http/cmd_list.go b/server/http/cmd_list.go new file mode 100644 index 0000000..3f83b8e --- /dev/null +++ b/server/http/cmd_list.go @@ -0,0 +1,248 @@ +package http + +import ( + "fmt" + "github.com/siddontang/ledisdb/ledis" + "strconv" +) + +func lpushCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpush") + } + key := []byte(args[0]) + elems := make([][]byte, len(args[1:])) + for i, arg := range args[1:] { + elems[i] = []byte(arg) + } + + if n, err := db.LPush(key, elems...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func rpushCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "rpush") + } + + key := []byte(args[0]) + elems := make([][]byte, len(args[1:])) + for i, arg := range args[1:] { + elems[i] = []byte(arg) + } + if n, err := db.RPush(key, elems...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func lpopCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpop") + } + + key := []byte(args[0]) + + if v, err := db.LPop(key); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func rpopCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "rpop") + } + key := []byte(args[0]) + + if v, err := db.RPop(key); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func llenCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "llen") + } + + key := []byte(args[0]) + if n, err := db.LLen(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func lindexCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lindex") + } + + index, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return nil, ErrValue + } + key := []byte(args[0]) + + if v, err := db.LIndex(key, int32(index)); err != nil { + return nil, err + } else { + if v == nil { + return nil, nil + } + return ledis.String(v), nil + } +} + +func lrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lrange") + } + + start, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + stop, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + if vals, err := db.LRange(key, int32(start), int32(stop)); err != nil { + return nil, err + } else { + arr := make([]interface{}, len(vals)) + for i, v := range vals { + if v == nil { + arr[i] = nil + } else { + arr[i] = ledis.String(v) + } + } + return arr, nil + } +} + +func lclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lclear") + } + + key := []byte(args[0]) + if n, err := db.LClear(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func lmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lmclear") + } + + keys := make([][]byte, len(args)) + for i, arg := range args { + keys[i] = []byte(arg) + } + if n, err := db.LMclear(keys...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func lexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lexpire") + } + + duration, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + if v, err := db.LExpire(key, duration); err != nil { + return nil, err + } else { + return v, nil + } +} + +func lexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lexpireat") + } + + when, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + if v, err := db.LExpireAt(key, when); err != nil { + return nil, err + } else { + return v, nil + } +} + +func lttlCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lttl") + } + + key := []byte(args[0]) + if v, err := db.LTTL(key); err != nil { + return nil, err + } else { + return v, nil + } +} + +func lpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpersist") + } + key := []byte(args[0]) + if n, err := db.LPersist(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func init() { + register("lindex", lindexCommand) + register("llen", llenCommand) + register("lpop", lpopCommand) + register("lrange", lrangeCommand) + register("lpush", lpushCommand) + register("rpop", rpopCommand) + register("rpush", rpushCommand) + + //ledisdb special command + + register("lclear", lclearCommand) + register("lmclear", lmclearCommand) + register("lexpire", lexpireCommand) + register("lexpireat", lexpireAtCommand) + register("lttl", lttlCommand) + register("lpersist", lpersistCommand) +} diff --git a/server/http/cmd_zset.go b/server/http/cmd_zset.go new file mode 100644 index 0000000..f1ed15e --- /dev/null +++ b/server/http/cmd_zset.go @@ -0,0 +1,520 @@ +package http + +import ( + "errors" + "fmt" + "github.com/siddontang/ledisdb/ledis" + "math" + "strconv" + "strings" +) + +var errScoreOverflow = errors.New("zset score overflow") + +func zaddCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zadd") + } + + if len(args[1:])%2 != 0 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zadd") + } + + key := []byte(args[0]) + args = args[1:] + + params := make([]ledis.ScorePair, len(args)/2) + for i := 0; i < len(params); i++ { + score, err := strconv.ParseInt(args[2*i], 10, 64) + if err != nil { + return nil, ErrValue + } + + params[i].Score = score + params[i].Member = []byte(args[2*i+1]) + } + + if n, err := db.ZAdd(key, params...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zcardCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zcard") + } + + key := []byte(args[0]) + if n, err := db.ZCard(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zscore") + } + + key := []byte(args[0]) + member := []byte(args[1]) + + if s, err := db.ZScore(key, member); err != nil { + if err == ledis.ErrScoreMiss { + return nil, nil + } else { + return nil, err + } + } else { + return strconv.FormatInt(s, 10), nil + } +} + +func zremCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrem") + } + + key := []byte(args[0]) + members := make([][]byte, len(args[1:])) + for i, arg := range args[1:] { + members[i] = []byte(arg) + } + if n, err := db.ZRem(key, members...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zincrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zincrby") + } + + key := []byte(args[0]) + + delta, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + member := []byte(args[2]) + if v, err := db.ZIncrBy(key, delta, member); err != nil { + return nil, err + } else { + return strconv.FormatInt(v, 10), nil + } +} + +func zparseScoreRange(minBuf string, maxBuf string) (min int64, max int64, err error) { + if strings.ToLower(minBuf) == "-inf" { + min = math.MinInt64 + } else { + var lopen bool = false + + if len(minBuf) == 0 { + err = ErrValue + return + } + + if minBuf[0] == '(' { + lopen = true + minBuf = minBuf[1:] + } + + min, err = strconv.ParseInt(minBuf, 10, 64) + if err != nil { + err = ErrValue + return + } + + if min <= ledis.MinScore || min >= ledis.MaxScore { + err = errScoreOverflow + return + } + + if lopen { + min++ + } + } + + if strings.ToLower(maxBuf) == "+inf" { + max = math.MaxInt64 + } else { + var ropen = false + + if len(maxBuf) == 0 { + err = ErrValue + return + } + + if maxBuf[0] == '(' { + ropen = true + maxBuf = maxBuf[1:] + } + + max, err = strconv.ParseInt(maxBuf, 10, 64) + if err != nil { + err = ErrValue + return + } + + if max <= ledis.MinScore || max >= ledis.MaxScore { + err = errScoreOverflow + return + } + + if ropen { + max-- + } + } + return +} + +func zcountCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zcount") + } + + min, max, err := zparseScoreRange(args[1], args[2]) + if err != nil { + return nil, err + } + + if min > max { + return 0, nil + } + + key := []byte(args[0]) + if n, err := db.ZCount(key, min, max); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zrankCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrank") + } + key := []byte(args[0]) + member := []byte(args[1]) + + if n, err := db.ZRank(key, member); err != nil { + return nil, err + } else if n == -1 { + return nil, nil + } else { + return n, nil + } +} + +func zrevrankCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrank") + } + + key := []byte(args[0]) + member := []byte(args[1]) + if n, err := db.ZRevRank(key, member); err != nil { + return nil, err + } else if n == -1 { + return nil, nil + } else { + return n, nil + } +} + +func zremrangebyrankCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zremrangebyrank") + } + + key := []byte(args[0]) + + start, err := strconv.Atoi(args[1]) + if err != nil { + return nil, ErrValue + } + stop, err := strconv.Atoi(args[2]) + + if err != nil { + return nil, ErrValue + } + + if n, err := db.ZRemRangeByRank(key, start, stop); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zremrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zremrangebyscore") + } + + key := []byte(args[0]) + min, max, err := zparseScoreRange(args[1], args[2]) + if err != nil { + return nil, err + } + + if n, err := db.ZRemRangeByScore(key, min, max); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zrangeGeneric(db *ledis.DB, reverse bool, args ...string) (interface{}, error) { + + key := []byte(args[0]) + + start, err := strconv.Atoi(args[1]) + if err != nil { + return nil, ErrValue + } + + stop, err := strconv.Atoi(args[2]) + if err != nil { + return nil, ErrValue + } + + args = args[3:] + var withScores bool = false + + if len(args) > 0 && strings.ToLower(args[0]) == "withscores" { + withScores = true + } + + if datas, err := db.ZRangeGeneric(key, start, stop, reverse); err != nil { + return nil, err + } else { + return makeScorePairArray(datas, withScores), nil + } +} + +func makeScorePairArray(datas []ledis.ScorePair, withScores bool) []string { + var arr []string + if withScores { + arr = make([]string, 2*len(datas)) + for i, data := range datas { + arr[2*i] = ledis.String(data.Member) + arr[2*i+1] = strconv.FormatInt(data.Score, 10) + } + } else { + arr = make([]string, len(datas)) + for i, data := range datas { + arr[i] = ledis.String(data.Member) + } + } + return arr +} + +func zrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrange") + } + return zrangeGeneric(db, false, args...) +} + +func zrevrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrange") + } + return zrangeGeneric(db, true, args...) +} + +func zrangebyscoreGeneric(db *ledis.DB, reverse bool, args ...string) (interface{}, error) { + key := []byte(args[0]) + + var minScore, maxScore string + + if !reverse { + minScore, maxScore = args[1], args[2] + } else { + minScore, maxScore = args[2], args[1] + } + + min, max, err := zparseScoreRange(minScore, maxScore) + + if err != nil { + return nil, err + } + + args = args[3:] + + var withScores bool = false + + if len(args) > 0 && strings.ToLower(args[0]) == "withscores" { + withScores = true + args = args[1:] + } + + var offset int = 0 + var count int = -1 + + if len(args) > 0 { + if len(args) != 3 { + return nil, ErrSyntax + } + + if strings.ToLower(args[0]) != "limit" { + return nil, ErrSyntax + } + + if offset, err = strconv.Atoi(args[1]); err != nil { + return nil, ErrValue + } + + if count, err = strconv.Atoi(args[2]); err != nil { + return nil, ErrValue + } + } + + if offset < 0 { + return []interface{}{}, nil + } + + if datas, err := db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { + return nil, err + } else { + return makeScorePairArray(datas, withScores), nil + } +} + +func zrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrangebyscore") + } + return zrangebyscoreGeneric(db, false, args...) +} + +func zrevrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 3 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrangebyscore") + } + return zrangebyscoreGeneric(db, true, args...) +} + +func zclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zclear") + } + + key := []byte(args[0]) + if n, err := db.ZClear(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) < 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zmclear") + } + + keys := make([][]byte, len(args)) + for i, arg := range args { + keys[i] = []byte(arg) + } + if n, err := db.ZMclear(keys...); err != nil { + return nil, err + } else { + return n, nil + } +} + +func zexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zexpire") + } + + duration, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + if v, err := db.ZExpire(key, duration); err != nil { + return nil, err + } else { + return v, nil + } +} + +func zexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 2 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zexpireat") + } + + when, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return nil, ErrValue + } + + key := []byte(args[0]) + if v, err := db.ZExpireAt(key, when); err != nil { + return nil, err + } else { + return v, nil + } +} + +func zttlCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zttl") + } + + key := []byte(args[0]) + if v, err := db.ZTTL(key); err != nil { + return nil, err + } else { + return v, nil + } +} + +func zpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { + if len(args) != 1 { + return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zpersist") + } + + key := []byte(args[0]) + if n, err := db.ZPersist(key); err != nil { + return nil, err + } else { + return n, nil + } +} + +func init() { + register("zadd", zaddCommand) + register("zcard", zcardCommand) + register("zcount", zcountCommand) + register("zincrby", zincrbyCommand) + register("zrange", zrangeCommand) + register("zrangebyscore", zrangebyscoreCommand) + register("zrank", zrankCommand) + register("zrem", zremCommand) + register("zremrangebyrank", zremrangebyrankCommand) + register("zremrangebyscore", zremrangebyscoreCommand) + register("zrevrange", zrevrangeCommand) + register("zrevrank", zrevrankCommand) + register("zrevrangebyscore", zrevrangebyscoreCommand) + register("zscore", zscoreCommand) + + //ledisdb special command + + register("zclear", zclearCommand) + register("zmclear", zmclearCommand) + register("zexpire", zexpireCommand) + register("zexpireat", zexpireAtCommand) + register("zttl", zttlCommand) + register("zpersist", zpersistCommand) +} diff --git a/server/http/handler.go b/server/http/handler.go index d395534..0c9ae1b 100644 --- a/server/http/handler.go +++ b/server/http/handler.go @@ -42,8 +42,9 @@ func (h *CmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []string) { /* - the proper format of `path` is /cmd/arg1/arg2/../argN or /db/cmd/arg1/arg2/../argN - if `path` is the first kind, `db` will be 0 + this function extracts `db`, `cmd` and `args` from `path` + the proper format of `path` is /cmd/arg1/arg2/../argN or /db/cmd/arg1/arg2/../argN + if `path` is the first kind, `db` will be 0 */ substrings := strings.Split(strings.TrimLeft(path, "/"), "/") if len(substrings) == 1 { From 3f3ca8da63b66b655fe671f3b8a591e6da8b06b6 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Thu, 24 Jul 2014 11:41:53 +0800 Subject: [PATCH 07/51] modify error msg --- server/http/base.go | 6 +++--- server/http/cmd_zset.go | 11 +++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/server/http/base.go b/server/http/base.go index 6b1781d..013c3c0 100644 --- a/server/http/base.go +++ b/server/http/base.go @@ -8,13 +8,13 @@ import ( ) const ( - ERR_ARGUMENT_FORMAT = "ERR wrong number of arguments for '%s' command" + ERR_ARGUMENT_FORMAT = "wrong number of arguments for '%s' command" MSG_OK = "OK" ) var ( - ErrValue = errors.New("ERR value is not an integer or out of range") - ErrSyntax = errors.New("ERR syntax error") + ErrValue = errors.New("value is not an integer or out of range") + ErrSyntax = errors.New("syntax error") ) type commondFunc func(*ledis.DB, ...string) (interface{}, error) diff --git a/server/http/cmd_zset.go b/server/http/cmd_zset.go index f1ed15e..55341d5 100644 --- a/server/http/cmd_zset.go +++ b/server/http/cmd_zset.go @@ -288,8 +288,15 @@ func zrangeGeneric(db *ledis.DB, reverse bool, args ...string) (interface{}, err args = args[3:] var withScores bool = false - if len(args) > 0 && strings.ToLower(args[0]) == "withscores" { - withScores = true + if len(args) > 0 { + if len(args) != 1 { + return nil, ErrSyntax + } + if strings.ToLower(args[0]) == "withscores" { + withScores = true + } else { + return nil, ErrSyntax + } } if datas, err := db.ZRangeGeneric(key, start, stop, reverse); err != nil { From ebdc1ee3ae578870bc1d821d85d0a89259263dd5 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Thu, 24 Jul 2014 15:06:39 +0800 Subject: [PATCH 08/51] support bson, msgpack --- bootstrap.sh | 4 +- server/http/handler.go | 93 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index dbbf060..a892669 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -4,4 +4,6 @@ go get -u github.com/siddontang/go-log/log go get -u github.com/siddontang/go-websocket/websocket -go get -u github.com/siddontang/go-snappy/snappy \ No newline at end of file +go get -u github.com/siddontang/go-snappy/snappy +go get -u gopkg.in/mgo.v2/bson +go get -u github.com/ugorji/go/codec diff --git a/server/http/handler.go b/server/http/handler.go index 0c9ae1b..187f04e 100644 --- a/server/http/handler.go +++ b/server/http/handler.go @@ -7,6 +7,8 @@ import ( "fmt" "github.com/siddontang/go-log/log" "github.com/siddontang/ledisdb/ledis" + "github.com/ugorji/go/codec" + "gopkg.in/mgo.v2/bson" "strconv" "strings" ) @@ -15,29 +17,50 @@ type CmdHandler struct { Ldb *ledis.Ledis } +var allowedContentTypes = map[string]struct{}{ + "json": struct{}{}, + "bson": struct{}{}, + "msgpack": struct{}{}, +} + func (h *CmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { w.WriteHeader(http.StatusForbidden) return } + idx, cmd, args := h.parseReqPath(r.URL.Path) + + contentType := r.FormValue("type") + if contentType == "" { + contentType = "json" + } + contentType = strings.ToLower(contentType) + if _, ok := allowedContentTypes[contentType]; !ok { + h.writeError( + cmd, + fmt.Errorf("unsupported content type '%s', only json, bson, msgpack are supported", contentType), + w, + "json") + return + } cmdFunc := lookup(cmd) if cmdFunc == nil { - h.cmdNotFound(cmd, w) + h.cmdNotFound(cmd, w, contentType) return } var db *ledis.DB var err error if db, err = h.Ldb.Select(idx); err != nil { - h.serverError(cmd, err, w) + h.writeError(cmd, err, w, contentType) return } result, err := cmdFunc(db, args...) if err != nil { - h.serverError(cmd, err, w) + h.writeError(cmd, err, w, contentType) return } - h.write(cmd, result, w) + h.write(cmd, result, w, contentType) } func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []string) { @@ -60,20 +83,38 @@ func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []strin } return } -func (h *CmdHandler) cmdNotFound(cmd string, w http.ResponseWriter) { - result := [2]interface{}{ - false, - fmt.Sprintf("ERR unknown command '%s'", cmd), - } - h.write(cmd, result, w) +func (h *CmdHandler) cmdNotFound(cmd string, w http.ResponseWriter, contentType string) { + err := fmt.Errorf("unknown command '%s'", cmd) + h.writeError(cmd, err, w, contentType) } -func (h *CmdHandler) write(cmd string, result interface{}, w http.ResponseWriter) { +func (h *CmdHandler) write(cmd string, result interface{}, w http.ResponseWriter, contentType string) { m := map[string]interface{}{ cmd: result, } - buf, err := json.Marshal(&m) + switch contentType { + case "json": + writeJSON(&m, w) + case "bson": + writeBSON(&m, w) + case "msgpack": + writeMsgPack(&m, w) + default: + log.Error("invalid content type %s", contentType) + } +} + +func (h *CmdHandler) writeError(cmd string, err error, w http.ResponseWriter, contentType string) { + result := [2]interface{}{ + false, + fmt.Sprintf("ERR %s", err.Error()), + } + h.write(cmd, result, w, contentType) +} + +func writeJSON(resutl interface{}, w http.ResponseWriter) { + buf, err := json.Marshal(resutl) if err != nil { log.Error(err.Error()) return @@ -88,12 +129,30 @@ func (h *CmdHandler) write(cmd string, result interface{}, w http.ResponseWriter } } -func (h *CmdHandler) serverError(cmd string, err error, w http.ResponseWriter) { - result := [2]interface{}{ - false, - fmt.Sprintf("ERR %s", err.Error()), +func writeBSON(result interface{}, w http.ResponseWriter) { + buf, err := bson.Marshal(result) + if err != nil { + log.Error(err.Error()) + return + } + + w.Header().Set("Content-type", "application/octet-stream") + w.Header().Set("Content-Length", strconv.Itoa(len(buf))) + + _, err = w.Write(buf) + if err != nil { + log.Error(err.Error()) + } +} + +func writeMsgPack(result interface{}, w http.ResponseWriter) { + w.Header().Set("Content-type", "application/octet-stream") + + var mh codec.MsgpackHandle + enc := codec.NewEncoder(w, &mh) + if err := enc.Encode(result); err != nil { + log.Error(err.Error()) } - h.write(cmd, result, w) } type WsHandler struct { From 58cdace7dfb5f758ff32d03be7c6d8557f19f9f2 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Thu, 24 Jul 2014 15:36:25 +0800 Subject: [PATCH 09/51] modify unit test --- server/http/cmd_bit_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/server/http/cmd_bit_test.go b/server/http/cmd_bit_test.go index 21844db..203a90c 100644 --- a/server/http/cmd_bit_test.go +++ b/server/http/cmd_bit_test.go @@ -12,7 +12,7 @@ func TestBgetCommand(t *testing.T) { db.BSetBit([]byte("test_bget"), 2, 1) _, err := bgetCommand(db, "test_bget", "a", "b", "c") - if err == nil || err.Error() != "ERR wrong number of arguments for 'bget' command" { + if err == nil || err.Error() != "wrong number of arguments for 'bget' command" { t.Fatal("invalid err %v", err) } @@ -30,7 +30,7 @@ func TestBDeleteCommand(t *testing.T) { db := getTestDB() _, err := bdeleteCommand(db, "test_bdelete", "a", "b", "c") - if err == nil || err.Error() != "ERR wrong number of arguments for 'bdelete' command" { + if err == nil || err.Error() != "wrong number of arguments for 'bdelete' command" { t.Fatalf("invalid err %v", err) } @@ -57,7 +57,7 @@ func TestBDeleteCommand(t *testing.T) { func TestBSetbitCommand(t *testing.T) { db := getTestDB() _, err := bsetbitCommand(db, "test_bsetbit", "a", "b", "c") - if err == nil || err.Error() != "ERR wrong number of arguments for 'bsetbit' command" { + if err == nil || err.Error() != "wrong number of arguments for 'bsetbit' command" { t.Fatalf("invalid err %v", err) } n, err := bsetbitCommand(db, "test_bsetbit", "1", "1") @@ -80,7 +80,7 @@ func TestBMsetbitCommand(t *testing.T) { db := getTestDB() _, err := bmsetbitCommand(db, "test_bmsetbit", "a", "b", "c") - if err == nil || err.Error() != "ERR wrong number of arguments for 'bmsetbit' command" { + if err == nil || err.Error() != "wrong number of arguments for 'bmsetbit' command" { t.Fatalf("invalid err %v", err) } n, err := bmsetbitCommand(db, "test_bmsetbit", "1", "1", "3", "1") @@ -91,3 +91,11 @@ func TestBMsetbitCommand(t *testing.T) { t.Fatalf("wrong result: %v", n) } } + +func TestBCountCommand(t *testing.T) { + db := getTestDB() + _, err := bcountCommand(db, "test_bcount", "a", "b", "c") + if err == nil || err.Error() != "wrong number of arguments for 'bcount' command" { + t.Fatalf("invalid err %v", err) + } +} From c0f3c54f3b31a15596ed95485e58339718257938 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Thu, 24 Jul 2014 17:19:44 +0800 Subject: [PATCH 10/51] unit tests of cmd_bit --- server/http/cmd_bit.go | 8 +- server/http/cmd_bit_test.go | 175 +++++++++++++++++++++++++++++++++++- 2 files changed, 178 insertions(+), 5 deletions(-) diff --git a/server/http/cmd_bit.go b/server/http/cmd_bit.go index 538b3a9..3efb76e 100644 --- a/server/http/cmd_bit.go +++ b/server/http/cmd_bit.go @@ -106,12 +106,12 @@ func bcountCommand(db *ledis.DB, args ...string) (interface{}, error) { var start, end int64 = 0, -1 if argCnt > 1 { if start, err = strconv.ParseInt(args[1], 10, 32); err != nil { - return nil, err + return nil, ErrValue } } if argCnt > 2 { - if end, err = strconv.ParseInt(args[1], 10, 32); err != nil { - return nil, err + if end, err = strconv.ParseInt(args[2], 10, 32); err != nil { + return nil, ErrValue } } key := []byte(args[0]) @@ -148,7 +148,7 @@ func boptCommand(db *ledis.DB, args ...string) (interface{}, error) { case "not": op = ledis.OPnot default: - return nil, fmt.Errorf("ERR invalid argument '%s' for 'bopt' command", opDesc) + return nil, fmt.Errorf("invalid argument '%s' for 'bopt' command", opDesc) } if blen, err := db.BOperation(op, dstKey, srcKeys...); err != nil { return nil, err diff --git a/server/http/cmd_bit_test.go b/server/http/cmd_bit_test.go index 203a90c..5a13238 100644 --- a/server/http/cmd_bit_test.go +++ b/server/http/cmd_bit_test.go @@ -1,8 +1,9 @@ package http import ( - // "github.com/siddontang/ledisdb/ledis" + "fmt" "testing" + "time" ) func TestBgetCommand(t *testing.T) { @@ -98,4 +99,176 @@ func TestBCountCommand(t *testing.T) { if err == nil || err.Error() != "wrong number of arguments for 'bcount' command" { t.Fatalf("invalid err %v", err) } + + db.BSetBit([]byte("test_bcount"), 1, 1) + db.BSetBit([]byte("test_bcount"), 3, 1) + + cnt, err := bcountCommand(db, "test_bcount", "0", "3") + if err != nil { + t.Fatal(err.Error()) + } + if cnt.(int32) != 2 { + t.Fatal("invalid value", cnt) + } + + cnt, err = bcountCommand(db, "test_bcount", "2") + + if err != nil { + t.Fatal(err.Error()) + } + if cnt.(int32) != 1 { + t.Fatal("invalid value", cnt) + } + + cnt, err = bcountCommand(db, "test_bcount") + + if err != nil { + t.Fatal(err.Error()) + } + if cnt.(int32) != 2 { + t.Fatal("invalid value", cnt) + } +} + +func TestBOptCommand(t *testing.T) { + db := getTestDB() + _, err := boptCommand(db, "test_bopt") + if err == nil || err.Error() != "wrong number of arguments for 'bopt' command" { + t.Fatalf("invalid err %v", err) + } + + db.BSetBit([]byte("test_bopt_and_1"), 1, 1) + db.BSetBit([]byte("test_bopt_and_2"), 1, 1) + + _, err = boptCommand(db, "and", "test_bopt_and_3", "test_bopt_and_1", "test_bopt_and_2") + if err != nil { + t.Fatal(err.Error()) + } + + r, _ := db.BGet([]byte("test_bopt_and_3")) + if len(r) != 1 || r[0] != 2 { + t.Fatalf("invalid result %v", r) + } + + db.BSetBit([]byte("test_bopt_or_1"), 0, 1) + db.BSetBit([]byte("test_bopt_or_1"), 1, 1) + db.BSetBit([]byte("test_bopt_or_2"), 0, 1) + db.BSetBit([]byte("test_bopt_or_2"), 2, 1) + + _, err = boptCommand(db, "or", "test_bopt_or_3", "test_bopt_or_1", "test_bopt_or_2") + if err != nil { + t.Fatal(err.Error()) + } + + r, _ = db.BGet([]byte("test_bopt_or_3")) + if len(r) != 1 || r[0] != 7 { + t.Fatalf("invalid result %v", r) + } + + db.BSetBit([]byte("test_bopt_xor_1"), 0, 1) + db.BSetBit([]byte("test_bopt_xor_1"), 1, 1) + db.BSetBit([]byte("test_bopt_xor_2"), 0, 1) + db.BSetBit([]byte("test_bopt_xor_2"), 2, 1) + + _, err = boptCommand(db, "xor", "test_bopt_xor_3", "test_bopt_xor_1", "test_bopt_xor_2") + if err != nil { + t.Fatal(err.Error()) + } + + r, _ = db.BGet([]byte("test_bopt_xor_3")) + if len(r) != 1 || r[0] != 6 { + t.Fatalf("invalid result %v", r) + } + + db.BSetBit([]byte("test_bopt_not_1"), 0, 1) + db.BSetBit([]byte("test_bopt_not_1"), 1, 0) + _, err = boptCommand(db, "not", "test_bopt_not_2", "test_bopt_not_1") + if err != nil { + t.Fatal(err.Error()) + } + + r, _ = db.BGet([]byte("test_bopt_not_2")) + if len(r) != 1 || r[0] != 2 { + t.Fatalf("invalid result %v", r) + } + + _, err = boptCommand(db, "invalid_opt", "abc") + if err == nil || err.Error() != "invalid argument 'invalid_opt' for 'bopt' command" { + t.Fatal("invalid err ", err.Error()) + } +} + +func TestBExpireCommand(t *testing.T) { + db := getTestDB() + _, err := bexpireCommand(db, "test_bexpire", "a", "b") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bexpire") { + t.Fatalf("invalid err %v", err) + } + + db.BSetBit([]byte("test_bexpire"), 1, 1) + bexpireCommand(db, "test_bexpire", "1000") + + n, err := db.BTTL([]byte("test_bexpire")) + if err != nil { + t.Fatal(err.Error()) + } + if n == -1 { + t.Fatal("wrong result ", n) + } +} + +func TestBExpireAtCommand(t *testing.T) { + db := getTestDB() + _, err := bexpireatCommand(db, "test_bexpireat", "a", "b") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bexpireat") { + t.Fatalf("invalid err %v", err) + } + + db.BSetBit([]byte("test_bexpireat"), 1, 1) + expireAt := fmt.Sprintf("%d", time.Now().Unix()+100) + if _, err = bexpireatCommand(db, "test_bexpireat", expireAt); err != nil { + t.Fatal(err.Error()) + } + + n, err := db.BTTL([]byte("test_bexpireat")) + if err != nil { + t.Fatal(err.Error()) + } + if n == -1 { + t.Fatal("wrong result ", n) + } +} + +func TestBTTLCommand(t *testing.T) { + db := getTestDB() + + _, err := bttlCommand(db, "test_bttl", "a", "b") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bttl") { + t.Fatalf("invalid err %v", err) + } + + v, err := bttlCommand(db, "test_bttl") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestBPersistCommand(t *testing.T) { + + db := getTestDB() + _, err := bpersistCommand(db, "test_bpersist", "a", "b") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bpersist") { + t.Fatalf("invalid err %v", err) + } + + v, err := bpersistCommand(db, "test_bpersist") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } } From 42174ed78a55d26c899be0c5ea1c9023e4a45ee0 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 25 Jul 2014 11:55:56 +0800 Subject: [PATCH 11/51] unit test for cmd_hash.go --- server/http/base.go | 2 + server/http/cmd_hash.go | 9 +- server/http/cmd_hash_test.go | 369 +++++++++++++++++++++++++++++++++++ 3 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 server/http/cmd_hash_test.go diff --git a/server/http/base.go b/server/http/base.go index 013c3c0..38e592e 100644 --- a/server/http/base.go +++ b/server/http/base.go @@ -27,6 +27,8 @@ func register(name string, f commondFunc) { } regCmds[name] = f } + func lookup(name string) commondFunc { return regCmds[strings.ToLower(name)] + } diff --git a/server/http/cmd_hash.go b/server/http/cmd_hash.go index cf0955b..23fa3b2 100644 --- a/server/http/cmd_hash.go +++ b/server/http/cmd_hash.go @@ -172,10 +172,14 @@ func hkeysCommand(db *ledis.DB, args ...string) (interface{}, error) { return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hkeys") } key := []byte(args[0]) - if v, err := db.HKeys(key); err != nil { + if fields, err := db.HKeys(key); err != nil { return nil, err } else { - return v, nil + arr := make([]string, len(fields)) + for i, f := range fields { + arr[i] = ledis.String(f) + } + return arr, nil } } @@ -303,4 +307,5 @@ func init() { register("hexpireat", hexpireAtCommand) register("httl", httlCommand) register("hpersist", hpersistCommand) + } diff --git a/server/http/cmd_hash_test.go b/server/http/cmd_hash_test.go new file mode 100644 index 0000000..c95fde1 --- /dev/null +++ b/server/http/cmd_hash_test.go @@ -0,0 +1,369 @@ +package http + +import ( + "fmt" + "testing" + "time" +) + +func TestHSetCommand(t *testing.T) { + db := getTestDB() + _, err := hsetCommand(db, "test_hset") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hset") { + t.Fatalf("invalid err %v", err) + } + + n, err := hsetCommand(db, "test_hset", "f", "v") + if err != nil { + t.Fatal(err) + } + if n.(int64) != 1 { + t.Fatal("invalid result ", n) + } + v, err := db.HGet([]byte("test_hset"), []byte("f")) + if err != nil { + t.Fatal(err.Error()) + } + if string(v) != "v" { + t.Fatalf("invalid result %s", v) + } + +} + +func TestHGetCommand(t *testing.T) { + db := getTestDB() + _, err := hgetCommand(db, "test_hget") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hget") { + t.Fatalf("invalid err %v", err) + } + if _, err := db.HSet([]byte("test_hget"), []byte("f"), []byte("v")); err != nil { + t.Fatal(err.Error()) + } + + v, err := hgetCommand(db, "test_hget", "f") + if err != nil { + t.Fatal(err.Error()) + } + if v.(string) != "v" { + t.Fatal("invalid result ", v) + } + +} + +func TestHExistsCommand(t *testing.T) { + db := getTestDB() + _, err := hexistsCommand(db, "test_hexists") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexists") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hexists"), []byte("f"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + + v, err := hexistsCommand(db, "test_hexists", "f") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } + +} + +func TestDelCommand(t *testing.T) { + db := getTestDB() + _, err := hdelCommand(db, "test_hdel") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hdel") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hdel"), []byte("f"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + + v, err := hdelCommand(db, "test_hdel", "f") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } + + r, err := db.HGet([]byte("test_hdel"), []byte("f")) + if err != nil { + t.Fatal(err.Error()) + } + if r != nil { + t.Fatalf("invalid result %v", r) + } +} + +func TestHLenCommand(t *testing.T) { + db := getTestDB() + _, err := hlenCommand(db, "test_hlen", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hlen") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hlen"), []byte("f1"), []byte("v1")) + if err != nil { + t.Fatal(err.Error()) + } + _, err = db.HSet([]byte("test_hlen"), []byte("f2"), []byte("v2")) + + if err != nil { + t.Fatal(err.Error()) + } + + v, err := hlenCommand(db, "test_hlen") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 2 { + t.Fatal("invalid result ", v) + } +} + +func TestHIncrbyCommand(t *testing.T) { + db := getTestDB() + _, err := hincrbyCommand(db, "test_hincrby") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hincrby") { + t.Fatalf("invalid err %v", err) + } + _, err = db.HSet([]byte("test_hincrby"), []byte("f"), []byte("10")) + if err != nil { + t.Fatal(err.Error()) + } + + _, err = hincrbyCommand(db, "test_hincrby", "f", "x") + if err != ErrValue { + t.Fatal("invalid err ", err) + } + + v, err := hincrbyCommand(db, "test_hincrby", "f", "10") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 20 { + t.Fatal("invalid result ", v) + } +} + +func TestHMsetCommand(t *testing.T) { + db := getTestDB() + _, err := hmsetCommand(db, "test_hmset") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmset") { + t.Fatalf("invalid err %v", err) + } + + _, err = hmsetCommand(db, "test_hmset", "f1", "v1", "f2", "v2") + if err != nil { + t.Fatal(err.Error()) + } + + v, err := db.HGet([]byte("test_hmset"), []byte("f1")) + + if err != nil { + t.Fatal(err.Error()) + } + if string(v) != "v1" { + t.Fatalf("invalid result %s", v) + } + + v, err = db.HGet([]byte("test_hmset"), []byte("f2")) + + if err != nil { + t.Fatal(err.Error()) + } + if string(v) != "v2" { + t.Fatalf("invalid result %s", v) + } +} + +func TestHMgetCommand(t *testing.T) { + db := getTestDB() + _, err := hmgetCommand(db, "test_hmget") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmget") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hmget"), []byte("f1"), []byte("v1")) + if err != nil { + t.Fatal(err.Error()) + } + + _, err = db.HSet([]byte("test_hmget"), []byte("f2"), []byte("v2")) + if err != nil { + t.Fatal(err.Error()) + } + + v, err := hmgetCommand(db, "test_hmget", "f1", "f2") + + if err != nil { + t.Fatal(err.Error()) + } + arr := v.([]interface{}) + if len(arr) != 2 { + t.Fatalf("invalid arr %v", arr) + } + if arr[0].(string) != "v1" { + t.Fatal("invalid result ", v) + } + + if arr[1].(string) != "v2" { + t.Fatal("invalid result ", v) + } +} + +func TestHGetallCommand(t *testing.T) { + db := getTestDB() + _, err := hgetallCommand(db, "test_hgetall", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hgetall") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hgetall"), []byte("f"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + v, err := hgetallCommand(db, "test_hgetall") + if err != nil { + t.Fatal(err.Error()) + } + m := v.(map[string]string) + if m["f"] != "v" { + t.Fatal("invalid result ", v) + } +} + +func TestHKeysCommand(t *testing.T) { + db := getTestDB() + _, err := hkeysCommand(db, "test_hkeys", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hkeys") { + t.Fatalf("invalid err %v", err) + } + _, err = db.HSet([]byte("test_hkeys"), []byte("f"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + v, err := hkeysCommand(db, "test_hkeys") + if err != nil { + t.Fatal(err.Error()) + } + arr := v.([]string) + if arr[0] != "f" { + t.Fatal("invalid result ", v) + } + +} + +func TestHClearCommand(t *testing.T) { + db := getTestDB() + _, err := hclearCommand(db, "test_hclear", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hclear") { + t.Fatalf("invalid err %v", err) + } + + _, err = db.HSet([]byte("test_hclear"), []byte("f"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + + v, err := hclearCommand(db, "test_hclear") + + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } +} + +func TestHMclearCommand(t *testing.T) { + db := getTestDB() + _, err := hmclearCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmclear") { + t.Fatalf("invalid err %v", err) + } + + v, err := hmclearCommand(db, "test_hmclear1", "test_hmclear2") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 2 { + t.Fatal("invalid result ", v) + } +} + +func TestHExpireCommand(t *testing.T) { + db := getTestDB() + _, err := hexpireCommand(db, "test_hexpire") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexpire") { + t.Fatalf("invalid err %v", err) + } + v, err := hexpireCommand(db, "test_hexpire", "10") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestHExpireAtCommand(t *testing.T) { + db := getTestDB() + _, err := hexpireAtCommand(db, "test_hexpireat") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexpireat") { + t.Fatalf("invalid err %v", err) + } + + expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) + v, err := hexpireCommand(db, "test_hexpireat", expireAt) + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestHTTLCommand(t *testing.T) { + db := getTestDB() + _, err := httlCommand(db, "test_httl", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "httl") { + t.Fatalf("invalid err %v", err) + } + + v, err := httlCommand(db, "test_httl") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestHPersistCommand(t *testing.T) { + db := getTestDB() + _, err := hpersistCommand(db, "test_hpersist", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hpersist") { + t.Fatalf("invalid err %v", err) + } + + v, err := hpersistCommand(db, "test_hpersist") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} From ee690fd35c5d9f94745d2e0e2972006496b86ae7 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 25 Jul 2014 15:18:39 +0800 Subject: [PATCH 12/51] add unit test --- server/http/base.go | 8 +-- server/http/base_test.go | 6 +- server/http/cmd_kv_test.go | 116 +++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 server/http/cmd_kv_test.go diff --git a/server/http/base.go b/server/http/base.go index 38e592e..2d81907 100644 --- a/server/http/base.go +++ b/server/http/base.go @@ -17,18 +17,18 @@ var ( ErrSyntax = errors.New("syntax error") ) -type commondFunc func(*ledis.DB, ...string) (interface{}, error) +type commandFunc func(*ledis.DB, ...string) (interface{}, error) -var regCmds = map[string]commondFunc{} +var regCmds = map[string]commandFunc{} -func register(name string, f commondFunc) { +func register(name string, f commandFunc) { if _, ok := regCmds[strings.ToLower(name)]; ok { panic(fmt.Sprintf("%s has been registered", name)) } regCmds[name] = f } -func lookup(name string) commondFunc { +func lookup(name string) commandFunc { return regCmds[strings.ToLower(name)] } diff --git a/server/http/base_test.go b/server/http/base_test.go index 1f07b31..fee37f2 100644 --- a/server/http/base_test.go +++ b/server/http/base_test.go @@ -12,11 +12,11 @@ var ldb *ledis.Ledis func getTestDB() *ledis.DB { f := func() { var err error - if _, err := os.Stat("/tmp/test_http_api_db"); err == nil { - if err := os.RemoveAll("/tmp/test_http_api_db"); err != nil { + if _, err = os.Stat("/tmp/test_http_api_db"); err == nil { + if err = os.RemoveAll("/tmp/test_http_api_db"); err != nil { panic(err) } - } else if err != os.ErrNotExist { + } else if !os.IsNotExist(err) { panic(err) } var cfg ledis.Config diff --git a/server/http/cmd_kv_test.go b/server/http/cmd_kv_test.go new file mode 100644 index 0000000..0ff661e --- /dev/null +++ b/server/http/cmd_kv_test.go @@ -0,0 +1,116 @@ +package http + +import ( + "fmt" + "testing" +) + +func TestGetCommand(t *testing.T) { + db := getTestDB() + _, err := getCommand(db, "test_get", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "get") { + t.Fatal("invalid err ", err) + } + + err = db.Set([]byte("test_get"), []byte("v")) + if err != nil { + t.Fatal(err.Error()) + } + v, err := getCommand(db, "test_get") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(string) != "v" { + t.Fatalf("invalid result %v", v) + } + +} + +func TestSetCommand(t *testing.T) { + db := getTestDB() + _, err := setCommand(db, "test_set") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "set") { + t.Fatal("invalid err ", err) + } + v, err := setCommand(db, "test_set", "v") + if err != nil { + t.Fatal(err.Error()) + } + r := v.([]interface{}) + if len(r) != 2 { + t.Fatalf("invalid result %v", v) + } + + if r[0].(bool) != true { + t.Fatalf("invalid result %v", r[0]) + } + + if r[1].(string) != "OK" { + t.Fatalf("invalid result %v", r[1]) + } +} + +func TestGetsetCommand(t *testing.T) { + db := getTestDB() + _, err := getsetCommand(db, "test_getset") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "getset") { + t.Fatal("invalid err ", err) + } + + v, err := getsetCommand(db, "test_getset", "v") + if err != nil { + t.Fatal(err.Error()) + } + if v != nil { + t.Fatal("invalid result ", v) + } +} + +func TestSetnxCommand(t *testing.T) { + db := getTestDB() + _, err := setnxCommand(db, "test_setnx") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "setnx") { + t.Fatal("invalid err ", err) + } + v, err := setnxCommand(db, "test_setnx", "v") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } +} + +func TestExistsCommand(t *testing.T) { + db := getTestDB() + _, err := existsCommand(db, "test_exists", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "exists") { + t.Fatal("invalid err ", err) + } + v, err := existsCommand(db, "test_exists") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestIncrCommand(t *testing.T) { + db := getTestDB() + _, err := incrCommand(db, "test_incr", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "incr") { + t.Fatal("invalid err ", err) + } + v, err := incrCommand(db, "test_incr") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } + +} From deb30383c63ee1b98a90d7f86647ac4def18e76b Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 25 Jul 2014 16:17:45 +0800 Subject: [PATCH 13/51] add unit test --- server/http/cmd_bit_test.go | 4 +- server/http/cmd_hash_test.go | 2 +- server/http/cmd_kv_test.go | 145 +++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) diff --git a/server/http/cmd_bit_test.go b/server/http/cmd_bit_test.go index 5a13238..f5af288 100644 --- a/server/http/cmd_bit_test.go +++ b/server/http/cmd_bit_test.go @@ -14,7 +14,7 @@ func TestBgetCommand(t *testing.T) { _, err := bgetCommand(db, "test_bget", "a", "b", "c") if err == nil || err.Error() != "wrong number of arguments for 'bget' command" { - t.Fatal("invalid err %v", err) + t.Fatalf("invalid err %v", err) } r, err := bgetCommand(db, "test_bget") @@ -23,7 +23,7 @@ func TestBgetCommand(t *testing.T) { } str := r.(string) if str != "\x07" { - t.Fatal("wrong result of 'bget': %v", []byte(str)) + t.Fatalf("wrong result of 'bget': %v", []byte(str)) } } diff --git a/server/http/cmd_hash_test.go b/server/http/cmd_hash_test.go index c95fde1..bbca2ed 100644 --- a/server/http/cmd_hash_test.go +++ b/server/http/cmd_hash_test.go @@ -73,7 +73,7 @@ func TestHExistsCommand(t *testing.T) { } -func TestDelCommand(t *testing.T) { +func TestHDelCommand(t *testing.T) { db := getTestDB() _, err := hdelCommand(db, "test_hdel") if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hdel") { diff --git a/server/http/cmd_kv_test.go b/server/http/cmd_kv_test.go index 0ff661e..b85de30 100644 --- a/server/http/cmd_kv_test.go +++ b/server/http/cmd_kv_test.go @@ -3,6 +3,7 @@ package http import ( "fmt" "testing" + "time" ) func TestGetCommand(t *testing.T) { @@ -114,3 +115,147 @@ func TestIncrCommand(t *testing.T) { } } + +func TestDecrCommand(t *testing.T) { + db := getTestDB() + _, err := decrCommand(db, "test_decr", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "decr") { + t.Fatal("invalid err ", err) + } + + v, err := decrCommand(db, "test_decr") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestDelCommand(t *testing.T) { + db := getTestDB() + _, err := delCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "del") { + t.Fatal("invalid err ", err) + } + + v, err := delCommand(db, "test_del") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } +} + +func TestMsetCommand(t *testing.T) { + db := getTestDB() + _, err := msetCommand(db, "a", "b", "c") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "mset") { + t.Fatal("invalid err ", err) + } + + v, err := msetCommand(db, "test_mset", "v") + + if err != nil { + t.Fatal(err.Error()) + } + r := v.([]interface{}) + if len(r) != 2 { + t.Fatal("invalid result ", v) + } + if r[0].(bool) != true { + t.Fatal("invalid result ", r[0]) + } + + if r[1].(string) != "OK" { + t.Fatal("invalid result ", r[1]) + } +} + +func TestMgetCommand(t *testing.T) { + db := getTestDB() + _, err := mgetCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "mget") { + t.Fatal("invalid err ", err) + } + + v, err := mgetCommand(db, "test_mget") + + if err != nil { + t.Fatal(err.Error()) + } + arr := v.([]interface{}) + if arr[0] != nil { + t.Fatal("invalid result ", arr) + } +} + +func TestExpireCommand(t *testing.T) { + db := getTestDB() + _, err := expireCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "expire") { + t.Fatal("invalid err ", err) + } + v, err := expireCommand(db, "test_expire", "10") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestExpireAtCommand(t *testing.T) { + db := getTestDB() + _, err := expireAtCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "expireat") { + t.Fatal("invalid err ", err) + } + + expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) + v, err := expireAtCommand(db, "test_expireat", expireAt) + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestTTLCommand(t *testing.T) { + db := getTestDB() + _, err := ttlCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "ttl") { + t.Fatal("invalid err ", err) + } + + v, err := ttlCommand(db, "test_ttl") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestPersistCommand(t *testing.T) { + db := getTestDB() + _, err := persistCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "persist") { + t.Fatal("invalid err ", err) + } + + v, err := persistCommand(db, "test_persist") + + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} From a586fa2f8ff9827ebd9e4fd0955b6af7dc9c13af Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 25 Jul 2014 17:27:07 +0800 Subject: [PATCH 14/51] add unit tests --- server/http/cmd_list_test.go | 217 +++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 server/http/cmd_list_test.go diff --git a/server/http/cmd_list_test.go b/server/http/cmd_list_test.go new file mode 100644 index 0000000..f280f28 --- /dev/null +++ b/server/http/cmd_list_test.go @@ -0,0 +1,217 @@ +package http + +import ( + "fmt" + "testing" + "time" +) + +func TestLpushCommand(t *testing.T) { + db := getTestDB() + _, err := lpushCommand(db, "test_lpush") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpush") { + t.Fatal("invalid err ", err) + } + + v, err := lpushCommand(db, "test_lpush", "1", "2") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 2 { + t.Fatal("invalid result", v) + } +} + +func TestRpushCommand(t *testing.T) { + db := getTestDB() + _, err := rpushCommand(db, "test_rpush") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "rpush") { + t.Fatal("invalid err ", err) + } + + v, err := rpushCommand(db, "test_rpush", "1", "2") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 2 { + t.Fatal("invalid result", v) + } +} + +func TestLpopCommand(t *testing.T) { + db := getTestDB() + _, err := lpopCommand(db, "test_lpop", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpop") { + t.Fatal("invalid err ", err) + } + + v, err := lpopCommand(db, "test_lpop") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result", v) + } +} + +func TestRpopCommand(t *testing.T) { + db := getTestDB() + _, err := rpopCommand(db, "test_rpop", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "rpop") { + t.Fatal("invalid err ", err) + } + + v, err := rpopCommand(db, "test_lpop") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result", v) + } +} + +func TestLlenCommand(t *testing.T) { + db := getTestDB() + _, err := llenCommand(db, "test_llen", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "llen") { + t.Fatal("invalid err ", err) + } + + v, err := llenCommand(db, "test_llen") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result", v) + } +} + +func TestLindexCommand(t *testing.T) { + db := getTestDB() + _, err := lindexCommand(db, "test_lindex") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lindex") { + t.Fatal("invalid err ", err) + } + v, err := lindexCommand(db, "test_lindex", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result", v) + } +} + +func TestLrangeCommand(t *testing.T) { + db := getTestDB() + _, err := lrangeCommand(db, "test_lrange") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lrange") { + t.Fatal("invalid err ", err) + } + v, err := lrangeCommand(db, "test_lrange", "1", "2") + if err != nil { + t.Fatal(err.Error()) + } + + arr := v.([]interface{}) + if len(arr) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestLclearCommand(t *testing.T) { + db := getTestDB() + _, err := lclearCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lclear") { + t.Fatal("invalid err ", err) + } + v, err := lclearCommand(db, "test_lclear") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestLmclearCommand(t *testing.T) { + db := getTestDB() + _, err := lmclearCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lmclear") { + t.Fatal("invalid err ", err) + } + v, err := lmclearCommand(db, "test_lmclear") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } +} + +func TestLexpireCommand(t *testing.T) { + db := getTestDB() + _, err := lexpireCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lexpire") { + t.Fatal("invalid err ", err) + } + v, err := lexpireCommand(db, "test_lexpire", "10") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestLexpireAtCommand(t *testing.T) { + db := getTestDB() + _, err := lexpireAtCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lexpireat") { + t.Fatal("invalid err ", err) + } + expireAt := fmt.Sprintf("%d", time.Now().Unix()) + v, err := lexpireCommand(db, "test_lexpireat", expireAt) + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestLTTLCommand(t *testing.T) { + db := getTestDB() + _, err := lttlCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lttl") { + t.Fatal("invalid err ", err) + } + v, err := lttlCommand(db, "test_lttl") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestLpersistCommand(t *testing.T) { + db := getTestDB() + _, err := lpersistCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpersist") { + t.Fatal("invalid err ", err) + } + + v, err := lpersistCommand(db, "test_lpersist") + if err != nil { + t.Fatal(err.Error()) + } + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} From fab9d46c4d19422349be9792d0bdd96bcfa20e87 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 28 Jul 2014 16:35:46 +0800 Subject: [PATCH 15/51] unit test of zset --- server/http/cmd_zset_test.go | 299 +++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 server/http/cmd_zset_test.go diff --git a/server/http/cmd_zset_test.go b/server/http/cmd_zset_test.go new file mode 100644 index 0000000..27337d4 --- /dev/null +++ b/server/http/cmd_zset_test.go @@ -0,0 +1,299 @@ +package http + +import ( + "fmt" + "testing" + "time" +) + +func TestZAddCommand(t *testing.T) { + db := getTestDB() + _, err := zaddCommand(db, "test_zadd") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zadd") { + t.Fatal("invalid err ", err) + } + + v, err := zaddCommand(db, "test_zadd", "10", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } + +} + +func TestZCardCommand(t *testing.T) { + db := getTestDB() + _, err := zcardCommand(db, "test_zcard", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zcard") { + t.Fatal("invalid err ", err) + } + + v, err := zcardCommand(db, "test_zcard") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZScore(t *testing.T) { + db := getTestDB() + _, err := zscoreCommand(db, "test_zscore") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zscore") { + t.Fatal("invalid err ", err) + } + v, err := zscoreCommand(db, "test_zscore", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result ", v) + } +} + +func TestZRemCommand(t *testing.T) { + db := getTestDB() + _, err := zremCommand(db, "test_zrem") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrem") { + t.Fatal("invalid err ", err) + } + v, err := zremCommand(db, "test_zrem", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZIncrbyCommand(t *testing.T) { + db := getTestDB() + _, err := zincrbyCommand(db, "test_zincrby") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zincrby") { + t.Fatal("invalid err ", err) + } + v, err := zincrbyCommand(db, "test_zincrby", "10", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(string) != "10" { + t.Fatal("invalid result ", v) + } +} + +func TestZCountCommand(t *testing.T) { + db := getTestDB() + _, err := zcountCommand(db, "test_zcount") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zcount") { + t.Fatal("invalid err ", err) + } + v, err := zcountCommand(db, "test_zcount", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZRankCommand(t *testing.T) { + db := getTestDB() + _, err := zrankCommand(db, "test_zrank") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrank") { + t.Fatal("invalid err ", err) + } + v, err := zrankCommand(db, "test_zcount", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result ", v) + } +} + +func TestZRevrankCommand(t *testing.T) { + db := getTestDB() + _, err := zrevrankCommand(db, "test_zrevrank") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrevrank") { + t.Fatal("invalid err ", err) + } + v, err := zrevrankCommand(db, "test_zrevrank", "m") + if err != nil { + t.Fatal(err.Error()) + } + + if v != nil { + t.Fatal("invalid result ", v) + } +} + +func TestZRemrangebyrankCommand(t *testing.T) { + db := getTestDB() + _, err := zremrangebyrankCommand(db, "test_zremrangebyrank") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zremrangebyrank") { + t.Fatal("invalid err ", err) + } + v, err := zremrangebyrankCommand(db, "test_zremrangebyrank", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZRemrangebyscore(t *testing.T) { + db := getTestDB() + _, err := zremrangebyscoreCommand(db, "test_zremrangebyscore") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zremrangebyscore") { + t.Fatal("invalid err ", err) + } + v, err := zremrangebyscoreCommand(db, "test_zremrangebyscore", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZRangeCommand(t *testing.T) { + db := getTestDB() + _, err := zrangeCommand(db, "test_zrange") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrange") { + t.Fatal("invalid err ", err) + } + v, err := zrangeCommand(db, "test_zrange", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if len(v.([]string)) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZRangebyscoreCommand(t *testing.T) { + db := getTestDB() + _, err := zrangebyscoreCommand(db, "test_zrangebyscore") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrangebyscore") { + t.Fatal("invalid err ", err) + } + v, err := zrangebyscoreCommand(db, "test_zrangebyscore", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if len(v.([]string)) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZRevrangebyscoreCommand(t *testing.T) { + db := getTestDB() + _, err := zrevrangebyscoreCommand(db, "test_zrevrangebyscore") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrevrangebyscore") { + t.Fatal("invalid err ", err) + } + v, err := zrevrangebyscoreCommand(db, "test_zrevrangebyscore", "0", "1") + if err != nil { + t.Fatal(err.Error()) + } + + if len(v.([]string)) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZMclearCommand(t *testing.T) { + db := getTestDB() + _, err := zmclearCommand(db) + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zmclear") { + t.Fatal("invalid err ", err) + } + v, err := zmclearCommand(db, "test_zmclear") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 1 { + t.Fatal("invalid result ", v) + } +} + +func TestZExpireCommand(t *testing.T) { + db := getTestDB() + _, err := zexpireCommand(db, "test_zexpire") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zexpire") { + t.Fatal("invalid err ", err) + } + v, err := zexpireCommand(db, "test_zexpire", "10") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZExpireAtCommand(t *testing.T) { + db := getTestDB() + _, err := zexpireAtCommand(db, "test_zexpireat") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zexpireat") { + t.Fatal("invalid err ", err) + } + expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) + v, err := zexpireAtCommand(db, "test_zexpire", expireAt) + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} + +func TestZTTLCommand(t *testing.T) { + db := getTestDB() + _, err := zttlCommand(db, "test_zttl", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zttl") { + t.Fatal("invalid err ", err) + } + v, err := zttlCommand(db, "test_zttl") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != -1 { + t.Fatal("invalid result ", v) + } +} + +func TestZPersistCommand(t *testing.T) { + db := getTestDB() + _, err := zpersistCommand(db, "test_zpersist", "a") + if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zpersist") { + t.Fatal("invalid err ", err) + } + v, err := zpersistCommand(db, "test_zpersist") + if err != nil { + t.Fatal(err.Error()) + } + + if v.(int64) != 0 { + t.Fatal("invalid result ", v) + } +} From f8e9308a0de67d65e8436bee31195eeced1b8b43 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 29 Jul 2014 11:02:13 +0800 Subject: [PATCH 16/51] cancel supporting websocket --- bootstrap.sh | 1 - server/app.go | 1 - server/http/handler.go | 8 -------- 3 files changed, 10 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index a892669..26bad6c 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -3,7 +3,6 @@ . ./dev.sh go get -u github.com/siddontang/go-log/log -go get -u github.com/siddontang/go-websocket/websocket go get -u github.com/siddontang/go-snappy/snappy go get -u gopkg.in/mgo.v2/bson go get -u github.com/ugorji/go/codec diff --git a/server/app.go b/server/app.go index 7fbcb65..3c13841 100644 --- a/server/app.go +++ b/server/app.go @@ -134,7 +134,6 @@ func (app *App) httpServe() { mux := http.NewServeMux() - mux.Handle("/ws", &WsHandler{app.Ledis()}) mux.Handle("/", &CmdHandler{app.Ledis()}) svr := http.Server{Handler: mux} diff --git a/server/http/handler.go b/server/http/handler.go index 187f04e..b958054 100644 --- a/server/http/handler.go +++ b/server/http/handler.go @@ -154,11 +154,3 @@ func writeMsgPack(result interface{}, w http.ResponseWriter) { log.Error(err.Error()) } } - -type WsHandler struct { - Ldb *ledis.Ledis -} - -func (h *WsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("ws handler")) -} From 7ba2cc6d61a8a09e84c1d42c3fb48de02290c35f Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 29 Jul 2014 15:27:43 +0800 Subject: [PATCH 17/51] add readme --- server/http/readme.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 server/http/readme.md diff --git a/server/http/readme.md b/server/http/readme.md new file mode 100644 index 0000000..c2447fb --- /dev/null +++ b/server/http/readme.md @@ -0,0 +1,42 @@ +##HTTP Interface +LedisDB provoids http interfaces for most commands. +####Request +The proper url format is + + http://host:port[/db]/cmd/arg1/arg2/.../argN[?type=type] + +'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a cumstom content type, can be json, bson or msgpack, json is default. + + +####Response + +The response format is + + { cmd: return_value } + +or + + { cmd: [success, message] } + +'return_value' stands for the output of 'cmd', it can be a number, a string, a list, or a hash. If the return value is just a descriptive message, the second format will be taken, and 'success', a boolean value, indicates whether it is successful. + +####Example +#####Curl + + curl http://127.0.0.1:11181/SET/hello/world + → {"SET":[true,"OK"]} + + curl http://127.0.0.1:11181/0/GET/hello?type=json + → {"GET":"world"} + +#####Python +Requires [msgpack-python](https://pypi.python.org/pypi/msgpack-python) and [requests](https://pypi.python.org/pypi/requests/) + + >>> import requests + >>> import msgpack + + >>> requests.get("http://127.0.0.1:11181/0/SET/hello/world") + >>> r = requests.get("http://127.0.0.1:11181/0/GET/hello?type=msgpack") + >>> msgpack.unpackb(r.content) + >>> {"GET":"world"} + From d4da8fab69ee9b03aceb59c5b8ceafb7dd547c7a Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 29 Jul 2014 15:31:50 +0800 Subject: [PATCH 18/51] Update readme.md --- server/http/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/http/readme.md b/server/http/readme.md index c2447fb..b87ea15 100644 --- a/server/http/readme.md +++ b/server/http/readme.md @@ -1,5 +1,5 @@ ##HTTP Interface -LedisDB provoids http interfaces for most commands. +LedisDB provides http interfaces for most commands. ####Request The proper url format is From 895613ca5bb06096a060de7dbd1d99d0e8ff0c5c Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 29 Jul 2014 15:33:35 +0800 Subject: [PATCH 19/51] Update readme.md --- server/http/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/http/readme.md b/server/http/readme.md index b87ea15..6e05c43 100644 --- a/server/http/readme.md +++ b/server/http/readme.md @@ -5,7 +5,7 @@ The proper url format is http://host:port[/db]/cmd/arg1/arg2/.../argN[?type=type] -'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a cumstom content type, can be json, bson or msgpack, json is default. +'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a custom content type, can be json, bson or msgpack, json is default. ####Response From 14767ecadba3e431c5d797f1e15a917fb166d788 Mon Sep 17 00:00:00 2001 From: silentsai Date: Thu, 31 Jul 2014 14:38:20 +0800 Subject: [PATCH 20/51] abstract the read / write in server client --- server/app.go | 2 +- server/client.go | 259 ++++++++------------------------------ server/cmd_bit.go | 22 ++-- server/cmd_hash.go | 34 ++--- server/cmd_kv.go | 32 ++--- server/cmd_list.go | 26 ++-- server/cmd_replication.go | 6 +- server/cmd_zset.go | 46 +++---- server/command.go | 6 +- server/http/http_io.go | 95 ++++++++++++++ server/replication.go | 26 ++-- server/tcp_io.go | 245 ++++++++++++++++++++++++++++++++++++ 12 files changed, 492 insertions(+), 307 deletions(-) create mode 100644 server/http/http_io.go create mode 100644 server/tcp_io.go diff --git a/server/app.go b/server/app.go index 3c13841..cc95575 100644 --- a/server/app.go +++ b/server/app.go @@ -123,7 +123,7 @@ func (app *App) Run() { continue } - newClient(conn, app) + newTcpClient(conn, app) } } diff --git a/server/client.go b/server/client.go index 11c9f97..22200a1 100644 --- a/server/client.go +++ b/server/client.go @@ -1,7 +1,6 @@ package server import ( - "bufio" "bytes" "errors" "github.com/siddontang/go-log/log" @@ -9,7 +8,6 @@ import ( "io" "net" "runtime" - "strconv" "strings" "time" ) @@ -21,40 +19,56 @@ type client struct { ldb *ledis.Ledis db *ledis.DB - c net.Conn - rb *bufio.Reader - wb *bufio.Writer + ctx clientContext + resp responseWriter + req requestReader cmd string args [][]byte reqC chan error - syncBuf bytes.Buffer - compressBuf []byte - - logBuf bytes.Buffer + syncBuf bytes.Buffer + logBuf bytes.Buffer } -func newClient(c net.Conn, app *App) { - co := new(client) +type clientContext interface { + addr() string + release() +} - co.app = app - co.ldb = app.ldb - //use default db - co.db, _ = app.ldb.Select(0) - co.c = c +type requestReader interface { + // readLine func() ([]byte, error) + read() ([][]byte, error) +} - co.rb = bufio.NewReaderSize(c, 256) - co.wb = bufio.NewWriterSize(c, 256) +type responseWriter interface { + writeError(error) + writeStatus(string) + writeInteger(int64) + writeBulk([]byte) + writeArray([]interface{}) + writeSliceArray([][]byte) + writeFVPairArray([]ledis.FVPair) + writeScorePairArray([]ledis.ScorePair, bool) + writeBulkFrom(int64, io.Reader) + flush() +} - co.reqC = make(chan error, 1) +func newClient(app *App) *client { + c := new(client) - co.compressBuf = make([]byte, 256) + c.app = app + c.ldb = app.ldb + c.db, _ = app.ldb.Select(0) - go co.run() + c.reqC = make(chan error, 1) + + c.compressBuf = make([]byte, 256) + + return c } func (c *client) run() { @@ -67,11 +81,11 @@ func (c *client) run() { log.Fatal("client run panic %s:%v", buf, e) } - c.c.Close() + c.ctx.release() }() for { - req, err := c.readRequest() + req, err := c.req.read() if err != nil { return } @@ -80,65 +94,6 @@ func (c *client) run() { } } -func (c *client) readLine() ([]byte, error) { - return ReadLine(c.rb) -} - -//A client sends to the Redis server a RESP Array consisting of just Bulk Strings. -func (c *client) readRequest() ([][]byte, error) { - l, err := c.readLine() - if err != nil { - return nil, err - } else if len(l) == 0 || l[0] != '*' { - return nil, errReadRequest - } - - var nparams int - if nparams, err = strconv.Atoi(ledis.String(l[1:])); err != nil { - return nil, err - } else if nparams <= 0 { - return nil, errReadRequest - } - - req := make([][]byte, 0, nparams) - var n int - for i := 0; i < nparams; i++ { - if l, err = c.readLine(); err != nil { - return nil, err - } - - if len(l) == 0 { - return nil, errReadRequest - } else if l[0] == '$' { - //handle resp string - if n, err = strconv.Atoi(ledis.String(l[1:])); err != nil { - return nil, err - } else if n == -1 { - req = append(req, nil) - } else { - buf := make([]byte, n) - if _, err = io.ReadFull(c.rb, buf); err != nil { - return nil, err - } - - if l, err = c.readLine(); err != nil { - return nil, err - } else if len(l) != 0 { - return nil, errors.New("bad bulk string format") - } - - req = append(req, buf) - - } - - } else { - return nil, errReadRequest - } - } - - return req, nil -} - func (c *client) handleRequest(req [][]byte) { var err error @@ -179,137 +134,27 @@ func (c *client) handleRequest(req [][]byte) { } } - c.app.access.Log(c.c.RemoteAddr().String(), duration.Nanoseconds()/1000000, c.logBuf.Bytes(), err) + c.app.access.Log(c.ctx.addr(), duration.Nanoseconds()/1000000, c.logBuf.Bytes(), err) } if err != nil { - c.writeError(err) + c.resp.writeError(err) } - c.wb.Flush() + c.resp.flush() } -func (c *client) writeError(err error) { - c.wb.Write(ledis.Slice("-ERR")) - if err != nil { - c.wb.WriteByte(' ') - c.wb.Write(ledis.Slice(err.Error())) - } - c.wb.Write(Delims) +func newTcpClient(conn net.Conn, app *App) { + c := newClient(app) + + c.ctx = newTcpContext(conn) + c.req = newTcpReader(conn) + c.resp = newTcpWriter(conn) + + go c.run() } -func (c *client) writeStatus(status string) { - c.wb.WriteByte('+') - c.wb.Write(ledis.Slice(status)) - c.wb.Write(Delims) -} - -func (c *client) writeInteger(n int64) { - c.wb.WriteByte(':') - c.wb.Write(ledis.StrPutInt64(n)) - c.wb.Write(Delims) -} - -func (c *client) writeBulk(b []byte) { - c.wb.WriteByte('$') - if b == nil { - c.wb.Write(NullBulk) - } else { - c.wb.Write(ledis.Slice(strconv.Itoa(len(b)))) - c.wb.Write(Delims) - c.wb.Write(b) - } - - c.wb.Write(Delims) -} - -func (c *client) writeArray(ay []interface{}) { - c.wb.WriteByte('*') - if ay == nil { - c.wb.Write(NullArray) - c.wb.Write(Delims) - } else { - c.wb.Write(ledis.Slice(strconv.Itoa(len(ay)))) - c.wb.Write(Delims) - - for i := 0; i < len(ay); i++ { - switch v := ay[i].(type) { - case []interface{}: - c.writeArray(v) - case []byte: - c.writeBulk(v) - case nil: - c.writeBulk(nil) - case int64: - c.writeInteger(v) - default: - panic("invalid array type") - } - } - } -} - -func (c *client) writeSliceArray(ay [][]byte) { - c.wb.WriteByte('*') - if ay == nil { - c.wb.Write(NullArray) - c.wb.Write(Delims) - } else { - c.wb.Write(ledis.Slice(strconv.Itoa(len(ay)))) - c.wb.Write(Delims) - - for i := 0; i < len(ay); i++ { - c.writeBulk(ay[i]) - } - } -} - -func (c *client) writeFVPairArray(ay []ledis.FVPair) { - c.wb.WriteByte('*') - if ay == nil { - c.wb.Write(NullArray) - c.wb.Write(Delims) - } else { - c.wb.Write(ledis.Slice(strconv.Itoa(len(ay) * 2))) - c.wb.Write(Delims) - - for i := 0; i < len(ay); i++ { - c.writeBulk(ay[i].Field) - c.writeBulk(ay[i].Value) - } - } -} - -func (c *client) writeScorePairArray(ay []ledis.ScorePair, withScores bool) { - c.wb.WriteByte('*') - if ay == nil { - c.wb.Write(NullArray) - c.wb.Write(Delims) - } else { - if withScores { - c.wb.Write(ledis.Slice(strconv.Itoa(len(ay) * 2))) - c.wb.Write(Delims) - } else { - c.wb.Write(ledis.Slice(strconv.Itoa(len(ay)))) - c.wb.Write(Delims) - - } - - for i := 0; i < len(ay); i++ { - c.writeBulk(ay[i].Member) - - if withScores { - c.writeBulk(ledis.StrPutInt64(ay[i].Score)) - } - } - } -} - -func (c *client) writeBulkFrom(n int64, rb io.Reader) { - c.wb.WriteByte('$') - c.wb.Write(ledis.Slice(strconv.FormatInt(n, 10))) - c.wb.Write(Delims) - - io.Copy(c.wb, rb) - c.wb.Write(Delims) -} +// func newHttpClient(w http.ResponseWriter, r *http.Request, app *App) { +// c := newClient(app) +// go c.run() +// } diff --git a/server/cmd_bit.go b/server/cmd_bit.go index cebb8dc..ec887a2 100644 --- a/server/cmd_bit.go +++ b/server/cmd_bit.go @@ -14,7 +14,7 @@ func bgetCommand(c *client) error { if v, err := c.db.BGet(args[0]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil } @@ -28,7 +28,7 @@ func bdeleteCommand(c *client) error { if n, err := c.db.BDelete(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil } @@ -56,7 +56,7 @@ func bsetbitCommand(c *client) error { if ori, err := c.db.BSetBit(args[0], offset, uint8(val)); err != nil { return err } else { - c.writeInteger(int64(ori)) + c.resp.writeInteger(int64(ori)) } return nil } @@ -75,7 +75,7 @@ func bgetbitCommand(c *client) error { if v, err := c.db.BGetBit(args[0], offset); err != nil { return err } else { - c.writeInteger(int64(v)) + c.resp.writeInteger(int64(v)) } return nil } @@ -116,7 +116,7 @@ func bmsetbitCommand(c *client) error { if place, err := c.db.BMSetBit(key, pairs...); err != nil { return err } else { - c.writeInteger(place) + c.resp.writeInteger(place) } return nil } @@ -151,7 +151,7 @@ func bcountCommand(c *client) error { if cnt, err := c.db.BCount(args[0], start, end); err != nil { return err } else { - c.writeInteger(int64(cnt)) + c.resp.writeInteger(int64(cnt)) } return nil } @@ -183,7 +183,7 @@ func boptCommand(c *client) error { if blen, err := c.db.BOperation(op, dstKey, srcKeys...); err != nil { return err } else { - c.writeInteger(int64(blen)) + c.resp.writeInteger(int64(blen)) } return nil } @@ -202,7 +202,7 @@ func bexpireCommand(c *client) error { if v, err := c.db.BExpire(args[0], duration); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -222,7 +222,7 @@ func bexpireatCommand(c *client) error { if v, err := c.db.BExpireAt(args[0], when); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -237,7 +237,7 @@ func bttlCommand(c *client) error { if v, err := c.db.BTTL(args[0]); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -252,7 +252,7 @@ func bpersistCommand(c *client) error { if n, err := c.db.BPersist(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil diff --git a/server/cmd_hash.go b/server/cmd_hash.go index cb3d752..0a736fc 100644 --- a/server/cmd_hash.go +++ b/server/cmd_hash.go @@ -13,7 +13,7 @@ func hsetCommand(c *client) error { if n, err := c.db.HSet(args[0], args[1], args[2]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -28,7 +28,7 @@ func hgetCommand(c *client) error { if v, err := c.db.HGet(args[0], args[1]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil @@ -48,7 +48,7 @@ func hexistsCommand(c *client) error { n = 0 } - c.writeInteger(n) + c.resp.writeInteger(n) } return nil } @@ -62,7 +62,7 @@ func hdelCommand(c *client) error { if n, err := c.db.HDel(args[0], args[1:]...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -77,7 +77,7 @@ func hlenCommand(c *client) error { if n, err := c.db.HLen(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -98,7 +98,7 @@ func hincrbyCommand(c *client) error { if n, err = c.db.HIncrBy(args[0], args[1], delta); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil } @@ -126,7 +126,7 @@ func hmsetCommand(c *client) error { if err := c.db.HMset(key, kvs...); err != nil { return err } else { - c.writeStatus(OK) + c.resp.writeStatus(OK) } return nil @@ -141,7 +141,7 @@ func hmgetCommand(c *client) error { if v, err := c.db.HMget(args[0], args[1:]...); err != nil { return err } else { - c.writeSliceArray(v) + c.resp.writeSliceArray(v) } return nil @@ -156,7 +156,7 @@ func hgetallCommand(c *client) error { if v, err := c.db.HGetAll(args[0]); err != nil { return err } else { - c.writeFVPairArray(v) + c.resp.writeFVPairArray(v) } return nil @@ -171,7 +171,7 @@ func hkeysCommand(c *client) error { if v, err := c.db.HKeys(args[0]); err != nil { return err } else { - c.writeSliceArray(v) + c.resp.writeSliceArray(v) } return nil @@ -186,7 +186,7 @@ func hvalsCommand(c *client) error { if v, err := c.db.HValues(args[0]); err != nil { return err } else { - c.writeSliceArray(v) + c.resp.writeSliceArray(v) } return nil @@ -201,7 +201,7 @@ func hclearCommand(c *client) error { if n, err := c.db.HClear(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -216,7 +216,7 @@ func hmclearCommand(c *client) error { if n, err := c.db.HMclear(args...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -236,7 +236,7 @@ func hexpireCommand(c *client) error { if v, err := c.db.HExpire(args[0], duration); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -256,7 +256,7 @@ func hexpireAtCommand(c *client) error { if v, err := c.db.HExpireAt(args[0], when); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -271,7 +271,7 @@ func httlCommand(c *client) error { if v, err := c.db.HTTL(args[0]); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -286,7 +286,7 @@ func hpersistCommand(c *client) error { if n, err := c.db.HPersist(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil diff --git a/server/cmd_kv.go b/server/cmd_kv.go index 1f61f5e..4462c83 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -13,7 +13,7 @@ func getCommand(c *client) error { if v, err := c.db.Get(args[0]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil } @@ -27,7 +27,7 @@ func setCommand(c *client) error { if err := c.db.Set(args[0], args[1]); err != nil { return err } else { - c.writeStatus(OK) + c.resp.writeStatus(OK) } return nil @@ -42,7 +42,7 @@ func getsetCommand(c *client) error { if v, err := c.db.GetSet(args[0], args[1]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil @@ -57,7 +57,7 @@ func setnxCommand(c *client) error { if n, err := c.db.SetNX(args[0], args[1]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -72,7 +72,7 @@ func existsCommand(c *client) error { if n, err := c.db.Exists(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -87,7 +87,7 @@ func incrCommand(c *client) error { if n, err := c.db.Incr(c.args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -102,7 +102,7 @@ func decrCommand(c *client) error { if n, err := c.db.Decr(c.args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -122,7 +122,7 @@ func incrbyCommand(c *client) error { if n, err := c.db.IncryBy(c.args[0], delta); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -142,7 +142,7 @@ func decrbyCommand(c *client) error { if n, err := c.db.DecrBy(c.args[0], delta); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -157,7 +157,7 @@ func delCommand(c *client) error { if n, err := c.db.Del(args...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -178,7 +178,7 @@ func msetCommand(c *client) error { if err := c.db.MSet(kvs...); err != nil { return err } else { - c.writeStatus(OK) + c.resp.writeStatus(OK) } return nil @@ -197,7 +197,7 @@ func mgetCommand(c *client) error { if v, err := c.db.MGet(args...); err != nil { return err } else { - c.writeSliceArray(v) + c.resp.writeSliceArray(v) } return nil @@ -217,7 +217,7 @@ func expireCommand(c *client) error { if v, err := c.db.Expire(args[0], duration); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -237,7 +237,7 @@ func expireAtCommand(c *client) error { if v, err := c.db.ExpireAt(args[0], when); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -252,7 +252,7 @@ func ttlCommand(c *client) error { if v, err := c.db.TTL(args[0]); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -267,7 +267,7 @@ func persistCommand(c *client) error { if n, err := c.db.Persist(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil diff --git a/server/cmd_list.go b/server/cmd_list.go index bc059ec..a4e971b 100644 --- a/server/cmd_list.go +++ b/server/cmd_list.go @@ -13,7 +13,7 @@ func lpushCommand(c *client) error { if n, err := c.db.LPush(args[0], args[1:]...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -28,7 +28,7 @@ func rpushCommand(c *client) error { if n, err := c.db.RPush(args[0], args[1:]...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -43,7 +43,7 @@ func lpopCommand(c *client) error { if v, err := c.db.LPop(args[0]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil @@ -58,7 +58,7 @@ func rpopCommand(c *client) error { if v, err := c.db.RPop(args[0]); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil @@ -73,7 +73,7 @@ func llenCommand(c *client) error { if n, err := c.db.LLen(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -93,7 +93,7 @@ func lindexCommand(c *client) error { if v, err := c.db.LIndex(args[0], int32(index)); err != nil { return err } else { - c.writeBulk(v) + c.resp.writeBulk(v) } return nil @@ -122,7 +122,7 @@ func lrangeCommand(c *client) error { if v, err := c.db.LRange(args[0], int32(start), int32(stop)); err != nil { return err } else { - c.writeSliceArray(v) + c.resp.writeSliceArray(v) } return nil @@ -137,7 +137,7 @@ func lclearCommand(c *client) error { if n, err := c.db.LClear(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -152,7 +152,7 @@ func lmclearCommand(c *client) error { if n, err := c.db.LMclear(args...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -172,7 +172,7 @@ func lexpireCommand(c *client) error { if v, err := c.db.LExpire(args[0], duration); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -192,7 +192,7 @@ func lexpireAtCommand(c *client) error { if v, err := c.db.LExpireAt(args[0], when); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -207,7 +207,7 @@ func lttlCommand(c *client) error { if v, err := c.db.LTTL(args[0]); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -222,7 +222,7 @@ func lpersistCommand(c *client) error { if n, err := c.db.LPersist(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil diff --git a/server/cmd_replication.go b/server/cmd_replication.go index c47be30..fe84191 100644 --- a/server/cmd_replication.go +++ b/server/cmd_replication.go @@ -35,7 +35,7 @@ func slaveofCommand(c *client) error { return err } - c.writeStatus(OK) + c.resp.writeStatus(OK) return nil } @@ -56,7 +56,7 @@ func fullsyncCommand(c *client) error { dumpFile.Seek(0, os.SEEK_SET) - c.writeBulkFrom(n, dumpFile) + c.resp.writeBulkFrom(n, dumpFile) name := dumpFile.Name() dumpFile.Close() @@ -112,7 +112,7 @@ func syncCommand(c *client) error { return err } - c.writeBulk(buf) + c.resp.writeBulk(buf) } return nil diff --git a/server/cmd_zset.go b/server/cmd_zset.go index eb25073..b697453 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -39,7 +39,7 @@ func zaddCommand(c *client) error { if n, err := c.db.ZAdd(key, params...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -54,7 +54,7 @@ func zcardCommand(c *client) error { if n, err := c.db.ZCard(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -68,12 +68,12 @@ func zscoreCommand(c *client) error { if s, err := c.db.ZScore(args[0], args[1]); err != nil { if err == ledis.ErrScoreMiss { - c.writeBulk(nil) + c.resp.writeBulk(nil) } else { return err } } else { - c.writeBulk(ledis.StrPutInt64(s)) + c.resp.writeBulk(ledis.StrPutInt64(s)) } return nil @@ -88,7 +88,7 @@ func zremCommand(c *client) error { if n, err := c.db.ZRem(args[0], args[1:]...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -110,7 +110,7 @@ func zincrbyCommand(c *client) error { if v, err := c.db.ZIncrBy(key, delta, args[2]); err != nil { return err } else { - c.writeBulk(ledis.StrPutInt64(v)) + c.resp.writeBulk(ledis.StrPutInt64(v)) } return nil @@ -190,14 +190,14 @@ func zcountCommand(c *client) error { } if min > max { - c.writeInteger(0) + c.resp.writeInteger(0) return nil } if n, err := c.db.ZCount(args[0], min, max); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -212,9 +212,9 @@ func zrankCommand(c *client) error { if n, err := c.db.ZRank(args[0], args[1]); err != nil { return err } else if n == -1 { - c.writeBulk(nil) + c.resp.writeBulk(nil) } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -229,9 +229,9 @@ func zrevrankCommand(c *client) error { if n, err := c.db.ZRevRank(args[0], args[1]); err != nil { return err } else if n == -1 { - c.writeBulk(nil) + c.resp.writeBulk(nil) } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -253,7 +253,7 @@ func zremrangebyrankCommand(c *client) error { if n, err := c.db.ZRemRangeByRank(key, start, stop); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -274,7 +274,7 @@ func zremrangebyscoreCommand(c *client) error { if n, err := c.db.ZRemRangeByScore(key, min, max); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -315,7 +315,7 @@ func zrangeGeneric(c *client, reverse bool) error { if datas, err := c.db.ZRangeGeneric(key, start, stop, reverse); err != nil { return err } else { - c.writeScorePairArray(datas, withScores) + c.resp.writeScorePairArray(datas, withScores) } return nil } @@ -383,14 +383,14 @@ func zrangebyscoreGeneric(c *client, reverse bool) error { if offset < 0 { //for ledis, if offset < 0, a empty will return //so here we directly return a empty array - c.writeArray([]interface{}{}) + c.resp.writeArray([]interface{}{}) return nil } if datas, err := c.db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { return err } else { - c.writeScorePairArray(datas, withScores) + c.resp.writeScorePairArray(datas, withScores) } return nil @@ -413,7 +413,7 @@ func zclearCommand(c *client) error { if n, err := c.db.ZClear(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -428,7 +428,7 @@ func zmclearCommand(c *client) error { if n, err := c.db.ZMclear(args...); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil @@ -448,7 +448,7 @@ func zexpireCommand(c *client) error { if v, err := c.db.ZExpire(args[0], duration); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -468,7 +468,7 @@ func zexpireAtCommand(c *client) error { if v, err := c.db.ZExpireAt(args[0], when); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -483,7 +483,7 @@ func zttlCommand(c *client) error { if v, err := c.db.ZTTL(args[0]); err != nil { return err } else { - c.writeInteger(v) + c.resp.writeInteger(v) } return nil @@ -498,7 +498,7 @@ func zpersistCommand(c *client) error { if n, err := c.db.ZPersist(args[0]); err != nil { return err } else { - c.writeInteger(n) + c.resp.writeInteger(n) } return nil diff --git a/server/command.go b/server/command.go index b54e11e..23ca7bd 100644 --- a/server/command.go +++ b/server/command.go @@ -21,7 +21,7 @@ func register(name string, f CommandFunc) { } func pingCommand(c *client) error { - c.writeStatus(PONG) + c.resp.writeStatus(PONG) return nil } @@ -30,7 +30,7 @@ func echoCommand(c *client) error { return ErrCmdParams } - c.writeBulk(c.args[0]) + c.resp.writeBulk(c.args[0]) return nil } @@ -46,7 +46,7 @@ func selectCommand(c *client) error { return err } else { c.db = db - c.writeStatus(OK) + c.resp.writeStatus(OK) } } return nil diff --git a/server/http/http_io.go b/server/http/http_io.go new file mode 100644 index 0000000..013e9b8 --- /dev/null +++ b/server/http/http_io.go @@ -0,0 +1,95 @@ +package http + +import ( + "github.com/siddontang/ledisdb/ledis" + "io" + "net/http" +) + +type httpContext struct { +} + +type httpReader struct { + req *http.Request +} + +type httpWriter struct { + resp *http.ResponseWriter +} + +// http context + +func newHttpContext() *httpContext { + ctx := new(httpContext) + return ctx +} + +func (ctx *httpContext) addr() string { + + return "" +} + +func (ctx *httpContext) release() { + +} + +// http reader + +func newHttpReader(req *http.Request) *httpReader { + r := new(httpReader) + r.req = req + return r +} + +func (r *httpReader) read() ([][]byte, error) { + + return nil, nil +} + +// http writer + +func newHttpWriter(resp *http.ResponseWriter) *httpWriter { + w := new(httpWriter) + w.resp = resp + return w +} + +func (w *httpWriter) writeError(err error) { + +} + +func (w *httpWriter) writeStatus(status string) { + +} + +func (w *httpWriter) writeInteger(n int64) { + +} + +func (w *httpWriter) writeBulk(b []byte) { + +} + +func (w *httpWriter) writeArray(lst []interface{}) { + +} + +func (w *httpWriter) writeSliceArray(lst [][]byte) { + +} + +func (w *httpWriter) writeFVPairArray(lst []ledis.FVPair) { + +} + +func (w *httpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { + +} + +func (w *httpWriter) writeBulkFrom(n int64, rb io.Reader) { + +} + +func (w *httpWriter) flush() { + +} diff --git a/server/replication.go b/server/replication.go index 4d185a1..d02264d 100644 --- a/server/replication.go +++ b/server/replication.go @@ -72,8 +72,8 @@ func (m *MasterInfo) Load(filePath string) error { type master struct { sync.Mutex - c net.Conn - rb *bufio.Reader + conn net.Conn + rb *bufio.Reader app *App @@ -114,9 +114,9 @@ func (m *master) Close() { default: } - if m.c != nil { - m.c.Close() - m.c = nil + if m.conn != nil { + m.conn.Close() + m.conn = nil } m.wg.Wait() @@ -135,17 +135,17 @@ func (m *master) connect() error { return fmt.Errorf("no assign master addr") } - if m.c != nil { - m.c.Close() - m.c = nil + if m.conn != nil { + m.conn.Close() + m.conn = nil } - if c, err := net.Dial("tcp", m.info.Addr); err != nil { + if conn, err := net.Dial("tcp", m.info.Addr); err != nil { return err } else { - m.c = c + m.conn = conn - m.rb = bufio.NewReaderSize(m.c, 4096) + m.rb = bufio.NewReaderSize(m.conn, 4096) } return nil } @@ -248,7 +248,7 @@ var ( ) func (m *master) fullSync() error { - if _, err := m.c.Write(fullSyncCmd); err != nil { + if _, err := m.conn.Write(fullSyncCmd); err != nil { return err } @@ -291,7 +291,7 @@ func (m *master) sync() error { cmd := ledis.Slice(fmt.Sprintf(syncCmdFormat, len(logIndexStr), logIndexStr, len(logPosStr), logPosStr)) - if _, err := m.c.Write(cmd); err != nil { + if _, err := m.conn.Write(cmd); err != nil { return err } diff --git a/server/tcp_io.go b/server/tcp_io.go new file mode 100644 index 0000000..62095b9 --- /dev/null +++ b/server/tcp_io.go @@ -0,0 +1,245 @@ +package server + +import ( + "bufio" + "errors" + "github.com/siddontang/ledisdb/ledis" + "io" + "net" + "strconv" +) + +type tcpContext struct { + conn net.Conn +} + +type tcpWriter struct { + buff *bufio.Writer +} + +type tcpReader struct { + buff *bufio.Reader +} + +// tcp context + +func newTcpContext(conn net.Conn) *tcpContext { + ctx := new(tcpContext) + ctx.conn = conn + return ctx +} + +func (ctx *tcpContext) addr() string { + return ctx.conn.RemoteAddr().String() +} + +func (ctx *tcpContext) release() { + if ctx.conn != nil { + ctx.conn.Close() + ctx.conn = nil + } +} + +// tcp reader + +func newTcpReader(conn net.Conn) *tcpReader { + r := new(tcpReader) + r.buff = bufio.NewReaderSize(conn, 256) + return r +} + +func (r *tcpReader) readLine() ([]byte, error) { + return ReadLine(r.buff) +} + +//A client sends to the Redis server a RESP Array consisting of just Bulk Strings. +func (r *tcpReader) read() ([][]byte, error) { + l, err := r.readLine() + if err != nil { + return nil, err + } else if len(l) == 0 || l[0] != '*' { + return nil, errReadRequest + } + + var nparams int + if nparams, err = strconv.Atoi(ledis.String(l[1:])); err != nil { + return nil, err + } else if nparams <= 0 { + return nil, errReadRequest + } + + reqData := make([][]byte, 0, nparams) + var n int + for i := 0; i < nparams; i++ { + if l, err = r.readLine(); err != nil { + return nil, err + } + + if len(l) == 0 { + return nil, errReadRequest + } else if l[0] == '$' { + //handle resp string + if n, err = strconv.Atoi(ledis.String(l[1:])); err != nil { + return nil, err + } else if n == -1 { + reqData = append(reqData, nil) + } else { + buf := make([]byte, n) + if _, err = io.ReadFull(r.buff, buf); err != nil { + return nil, err + } + + if l, err = r.readLine(); err != nil { + return nil, err + } else if len(l) != 0 { + return nil, errors.New("bad bulk string format") + } + + reqData = append(reqData, buf) + + } + + } else { + return nil, errReadRequest + } + } + + return reqData, nil +} + +// tcp writer + +func newTcpWriter(conn net.Conn) *tcpWriter { + w := new(tcpWriter) + w.buff = bufio.NewWriterSize(conn, 256) + return w +} + +func (w *tcpWriter) writeError(err error) { + w.buff.Write(ledis.Slice("-ERR")) + if err != nil { + w.buff.WriteByte(' ') + w.buff.Write(ledis.Slice(err.Error())) + } + w.buff.Write(Delims) +} + +func (w *tcpWriter) writeStatus(status string) { + w.buff.WriteByte('+') + w.buff.Write(ledis.Slice(status)) + w.buff.Write(Delims) +} + +func (w *tcpWriter) writeInteger(n int64) { + w.buff.WriteByte(':') + w.buff.Write(ledis.StrPutInt64(n)) + w.buff.Write(Delims) +} + +func (w *tcpWriter) writeBulk(b []byte) { + w.buff.WriteByte('$') + if b == nil { + w.buff.Write(NullBulk) + } else { + w.buff.Write(ledis.Slice(strconv.Itoa(len(b)))) + w.buff.Write(Delims) + w.buff.Write(b) + } + + w.buff.Write(Delims) +} + +func (w *tcpWriter) writeArray(lst []interface{}) { + w.buff.WriteByte('*') + if lst == nil { + w.buff.Write(NullArray) + w.buff.Write(Delims) + } else { + w.buff.Write(ledis.Slice(strconv.Itoa(len(lst)))) + w.buff.Write(Delims) + + for i := 0; i < len(lst); i++ { + switch v := lst[i].(type) { + case []interface{}: + w.writeArray(v) + case []byte: + w.writeBulk(v) + case nil: + w.writeBulk(nil) + case int64: + w.writeInteger(v) + default: + panic("invalid array type") + } + } + } +} + +func (w *tcpWriter) writeSliceArray(lst [][]byte) { + w.buff.WriteByte('*') + if lst == nil { + w.buff.Write(NullArray) + w.buff.Write(Delims) + } else { + w.buff.Write(ledis.Slice(strconv.Itoa(len(lst)))) + w.buff.Write(Delims) + + for i := 0; i < len(lst); i++ { + w.writeBulk(lst[i]) + } + } +} + +func (w *tcpWriter) writeFVPairArray(lst []ledis.FVPair) { + w.buff.WriteByte('*') + if lst == nil { + w.buff.Write(NullArray) + w.buff.Write(Delims) + } else { + w.buff.Write(ledis.Slice(strconv.Itoa(len(lst) * 2))) + w.buff.Write(Delims) + + for i := 0; i < len(lst); i++ { + w.writeBulk(lst[i].Field) + w.writeBulk(lst[i].Value) + } + } +} + +func (w *tcpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { + w.buff.WriteByte('*') + if lst == nil { + w.buff.Write(NullArray) + w.buff.Write(Delims) + } else { + if withScores { + w.buff.Write(ledis.Slice(strconv.Itoa(len(lst) * 2))) + w.buff.Write(Delims) + } else { + w.buff.Write(ledis.Slice(strconv.Itoa(len(lst)))) + w.buff.Write(Delims) + + } + + for i := 0; i < len(lst); i++ { + w.writeBulk(lst[i].Member) + + if withScores { + w.writeBulk(ledis.StrPutInt64(lst[i].Score)) + } + } + } +} + +func (w *tcpWriter) writeBulkFrom(n int64, rb io.Reader) { + w.buff.WriteByte('$') + w.buff.Write(ledis.Slice(strconv.FormatInt(n, 10))) + w.buff.Write(Delims) + + io.Copy(w.buff, rb) + w.buff.Write(Delims) +} + +func (w *tcpWriter) flush() { + w.buff.Flush() +} From 715e35098cccba3a4ef75f11591b28aae145bec7 Mon Sep 17 00:00:00 2001 From: silentsai Date: Thu, 31 Jul 2014 18:28:19 +0800 Subject: [PATCH 21/51] seperate the behavior from client : client input , request handle --- server/app.go | 2 +- server/client.go | 160 --------------------- server/{tcp_io.go => client_resp.go} | 138 +++++++++++------- server/cmd_bit.go | 88 ++++++------ server/cmd_hash.go | 136 +++++++++--------- server/cmd_kv.go | 130 ++++++++--------- server/cmd_list.go | 104 +++++++------- server/cmd_replication.go | 36 ++--- server/cmd_zset.go | 176 +++++++++++------------ server/command.go | 24 ++-- server/request.go | 200 +++++++++++++++++++++++++++ 11 files changed, 637 insertions(+), 557 deletions(-) delete mode 100644 server/client.go rename server/{tcp_io.go => client_resp.go} (58%) create mode 100644 server/request.go diff --git a/server/app.go b/server/app.go index cc95575..781e097 100644 --- a/server/app.go +++ b/server/app.go @@ -123,7 +123,7 @@ func (app *App) Run() { continue } - newTcpClient(conn, app) + newClientRESP(conn, app) } } diff --git a/server/client.go b/server/client.go deleted file mode 100644 index 22200a1..0000000 --- a/server/client.go +++ /dev/null @@ -1,160 +0,0 @@ -package server - -import ( - "bytes" - "errors" - "github.com/siddontang/go-log/log" - "github.com/siddontang/ledisdb/ledis" - "io" - "net" - "runtime" - "strings" - "time" -) - -var errReadRequest = errors.New("invalid request protocol") - -type client struct { - app *App - ldb *ledis.Ledis - - db *ledis.DB - - ctx clientContext - resp responseWriter - req requestReader - - cmd string - args [][]byte - - reqC chan error - - compressBuf []byte - syncBuf bytes.Buffer - logBuf bytes.Buffer -} - -type clientContext interface { - addr() string - release() -} - -type requestReader interface { - // readLine func() ([]byte, error) - read() ([][]byte, error) -} - -type responseWriter interface { - writeError(error) - writeStatus(string) - writeInteger(int64) - writeBulk([]byte) - writeArray([]interface{}) - writeSliceArray([][]byte) - writeFVPairArray([]ledis.FVPair) - writeScorePairArray([]ledis.ScorePair, bool) - writeBulkFrom(int64, io.Reader) - flush() -} - -func newClient(app *App) *client { - c := new(client) - - c.app = app - c.ldb = app.ldb - c.db, _ = app.ldb.Select(0) - - c.reqC = make(chan error, 1) - - c.compressBuf = make([]byte, 256) - - return c -} - -func (c *client) run() { - defer func() { - if e := recover(); e != nil { - buf := make([]byte, 4096) - n := runtime.Stack(buf, false) - buf = buf[0:n] - - log.Fatal("client run panic %s:%v", buf, e) - } - - c.ctx.release() - }() - - for { - req, err := c.req.read() - if err != nil { - return - } - - c.handleRequest(req) - } -} - -func (c *client) handleRequest(req [][]byte) { - var err error - - start := time.Now() - - if len(req) == 0 { - err = ErrEmptyCommand - } else { - c.cmd = strings.ToLower(ledis.String(req[0])) - c.args = req[1:] - - f, ok := regCmds[c.cmd] - if !ok { - err = ErrNotFound - } else { - go func() { - c.reqC <- f(c) - }() - err = <-c.reqC - } - } - - duration := time.Since(start) - - if c.app.access != nil { - c.logBuf.Reset() - for i, r := range req { - left := 256 - c.logBuf.Len() - if left <= 0 { - break - } else if len(r) <= left { - c.logBuf.Write(r) - if i != len(req)-1 { - c.logBuf.WriteByte(' ') - } - } else { - c.logBuf.Write(r[0:left]) - } - } - - c.app.access.Log(c.ctx.addr(), duration.Nanoseconds()/1000000, c.logBuf.Bytes(), err) - } - - if err != nil { - c.resp.writeError(err) - } - - c.resp.flush() -} - -func newTcpClient(conn net.Conn, app *App) { - c := newClient(app) - - c.ctx = newTcpContext(conn) - c.req = newTcpReader(conn) - c.resp = newTcpWriter(conn) - - go c.run() -} - -// func newHttpClient(w http.ResponseWriter, r *http.Request, app *App) { -// c := newClient(app) -// go c.run() -// } diff --git a/server/tcp_io.go b/server/client_resp.go similarity index 58% rename from server/tcp_io.go rename to server/client_resp.go index 62095b9..4d0110a 100644 --- a/server/tcp_io.go +++ b/server/client_resp.go @@ -3,58 +3,81 @@ package server import ( "bufio" "errors" + "github.com/siddontang/go-log/log" "github.com/siddontang/ledisdb/ledis" "io" "net" + "runtime" "strconv" + "strings" ) -type tcpContext struct { +var errReadRequest = errors.New("invalid request protocol") + +type respClient struct { + app *App + ldb *ledis.Ledis + db *ledis.DB + conn net.Conn + rb *bufio.Reader + + req *requestContext + hdl *requestHandler } -type tcpWriter struct { +type respWriter struct { buff *bufio.Writer } -type tcpReader struct { - buff *bufio.Reader +func newClientRESP(conn net.Conn, app *App) { + c := new(respClient) + + c.app = app + c.conn = conn + c.ldb = app.ldb + c.db, _ = app.ldb.Select(0) + + c.rb = bufio.NewReaderSize(conn, 256) + + c.req = newRequestContext(app) + c.req.resp = newWriterRESP(conn) + + c.hdl = newRequestHandler(app) + + go c.run() } -// tcp context +func (c *respClient) run() { + defer func() { + if e := recover(); e != nil { + buf := make([]byte, 4096) + n := runtime.Stack(buf, false) + buf = buf[0:n] -func newTcpContext(conn net.Conn) *tcpContext { - ctx := new(tcpContext) - ctx.conn = conn - return ctx -} + log.Fatal("client run panic %s:%v", buf, e) + } -func (ctx *tcpContext) addr() string { - return ctx.conn.RemoteAddr().String() -} + c.conn.Close() + }() -func (ctx *tcpContext) release() { - if ctx.conn != nil { - ctx.conn.Close() - ctx.conn = nil + for { + reqData, err := c.readRequest() + if err != nil { + return + } + + c.handleRequest(reqData) } } -// tcp reader - -func newTcpReader(conn net.Conn) *tcpReader { - r := new(tcpReader) - r.buff = bufio.NewReaderSize(conn, 256) - return r -} - -func (r *tcpReader) readLine() ([]byte, error) { - return ReadLine(r.buff) +func (c *respClient) readLine() ([]byte, error) { + return ReadLine(c.rb) } //A client sends to the Redis server a RESP Array consisting of just Bulk Strings. -func (r *tcpReader) read() ([][]byte, error) { - l, err := r.readLine() +func (c *respClient) readRequest() ([][]byte, error) { + l, err := c.readLine() if err != nil { return nil, err } else if len(l) == 0 || l[0] != '*' { @@ -68,10 +91,10 @@ func (r *tcpReader) read() ([][]byte, error) { return nil, errReadRequest } - reqData := make([][]byte, 0, nparams) + req := make([][]byte, 0, nparams) var n int for i := 0; i < nparams; i++ { - if l, err = r.readLine(); err != nil { + if l, err = c.readLine(); err != nil { return nil, err } @@ -82,20 +105,20 @@ func (r *tcpReader) read() ([][]byte, error) { if n, err = strconv.Atoi(ledis.String(l[1:])); err != nil { return nil, err } else if n == -1 { - reqData = append(reqData, nil) + req = append(req, nil) } else { buf := make([]byte, n) - if _, err = io.ReadFull(r.buff, buf); err != nil { + if _, err = io.ReadFull(c.rb, buf); err != nil { return nil, err } - if l, err = r.readLine(); err != nil { + if l, err = c.readLine(); err != nil { return nil, err } else if len(l) != 0 { return nil, errors.New("bad bulk string format") } - reqData = append(reqData, buf) + req = append(req, buf) } @@ -104,18 +127,35 @@ func (r *tcpReader) read() ([][]byte, error) { } } - return reqData, nil + return req, nil } -// tcp writer +func (c *respClient) handleRequest(reqData [][]byte) { + req := c.req -func newTcpWriter(conn net.Conn) *tcpWriter { - w := new(tcpWriter) + req.db = c.db + req.remoteAddr = c.conn.RemoteAddr().String() + + if len(reqData) == 0 { + c.req.cmd = "" + c.req.args = reqData[0:0] + } else { + c.req.cmd = strings.ToLower(ledis.String(reqData[0])) + c.req.args = reqData[1:] + } + + c.hdl.postRequest(req) +} + +// response writer + +func newWriterRESP(conn net.Conn) *respWriter { + w := new(respWriter) w.buff = bufio.NewWriterSize(conn, 256) return w } -func (w *tcpWriter) writeError(err error) { +func (w *respWriter) writeError(err error) { w.buff.Write(ledis.Slice("-ERR")) if err != nil { w.buff.WriteByte(' ') @@ -124,19 +164,19 @@ func (w *tcpWriter) writeError(err error) { w.buff.Write(Delims) } -func (w *tcpWriter) writeStatus(status string) { +func (w *respWriter) writeStatus(status string) { w.buff.WriteByte('+') w.buff.Write(ledis.Slice(status)) w.buff.Write(Delims) } -func (w *tcpWriter) writeInteger(n int64) { +func (w *respWriter) writeInteger(n int64) { w.buff.WriteByte(':') w.buff.Write(ledis.StrPutInt64(n)) w.buff.Write(Delims) } -func (w *tcpWriter) writeBulk(b []byte) { +func (w *respWriter) writeBulk(b []byte) { w.buff.WriteByte('$') if b == nil { w.buff.Write(NullBulk) @@ -149,7 +189,7 @@ func (w *tcpWriter) writeBulk(b []byte) { w.buff.Write(Delims) } -func (w *tcpWriter) writeArray(lst []interface{}) { +func (w *respWriter) writeArray(lst []interface{}) { w.buff.WriteByte('*') if lst == nil { w.buff.Write(NullArray) @@ -175,7 +215,7 @@ func (w *tcpWriter) writeArray(lst []interface{}) { } } -func (w *tcpWriter) writeSliceArray(lst [][]byte) { +func (w *respWriter) writeSliceArray(lst [][]byte) { w.buff.WriteByte('*') if lst == nil { w.buff.Write(NullArray) @@ -190,7 +230,7 @@ func (w *tcpWriter) writeSliceArray(lst [][]byte) { } } -func (w *tcpWriter) writeFVPairArray(lst []ledis.FVPair) { +func (w *respWriter) writeFVPairArray(lst []ledis.FVPair) { w.buff.WriteByte('*') if lst == nil { w.buff.Write(NullArray) @@ -206,7 +246,7 @@ func (w *tcpWriter) writeFVPairArray(lst []ledis.FVPair) { } } -func (w *tcpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { +func (w *respWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { w.buff.WriteByte('*') if lst == nil { w.buff.Write(NullArray) @@ -231,7 +271,7 @@ func (w *tcpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) } } -func (w *tcpWriter) writeBulkFrom(n int64, rb io.Reader) { +func (w *respWriter) writeBulkFrom(n int64, rb io.Reader) { w.buff.WriteByte('$') w.buff.Write(ledis.Slice(strconv.FormatInt(n, 10))) w.buff.Write(Delims) @@ -240,6 +280,6 @@ func (w *tcpWriter) writeBulkFrom(n int64, rb io.Reader) { w.buff.Write(Delims) } -func (w *tcpWriter) flush() { +func (w *respWriter) flush() { w.buff.Flush() } diff --git a/server/cmd_bit.go b/server/cmd_bit.go index ec887a2..d95e416 100644 --- a/server/cmd_bit.go +++ b/server/cmd_bit.go @@ -5,36 +5,36 @@ import ( "strings" ) -func bgetCommand(c *client) error { - args := c.args +func bgetCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.BGet(args[0]); err != nil { + if v, err := req.db.BGet(args[0]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func bdeleteCommand(c *client) error { - args := c.args +func bdeleteCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.BDelete(args[0]); err != nil { + if n, err := req.db.BDelete(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func bsetbitCommand(c *client) error { - args := c.args +func bsetbitCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -53,16 +53,16 @@ func bsetbitCommand(c *client) error { return err } - if ori, err := c.db.BSetBit(args[0], offset, uint8(val)); err != nil { + if ori, err := req.db.BSetBit(args[0], offset, uint8(val)); err != nil { return err } else { - c.resp.writeInteger(int64(ori)) + req.resp.writeInteger(int64(ori)) } return nil } -func bgetbitCommand(c *client) error { - args := c.args +func bgetbitCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -72,16 +72,16 @@ func bgetbitCommand(c *client) error { return err } - if v, err := c.db.BGetBit(args[0], offset); err != nil { + if v, err := req.db.BGetBit(args[0], offset); err != nil { return err } else { - c.resp.writeInteger(int64(v)) + req.resp.writeInteger(int64(v)) } return nil } -func bmsetbitCommand(c *client) error { - args := c.args +func bmsetbitCommand(req *requestContext) error { + args := req.args if len(args) < 3 { return ErrCmdParams } @@ -113,16 +113,16 @@ func bmsetbitCommand(c *client) error { pairs[i].Val = uint8(val) } - if place, err := c.db.BMSetBit(key, pairs...); err != nil { + if place, err := req.db.BMSetBit(key, pairs...); err != nil { return err } else { - c.resp.writeInteger(place) + req.resp.writeInteger(place) } return nil } -func bcountCommand(c *client) error { - args := c.args +func bcountCommand(req *requestContext) error { + args := req.args argCnt := len(args) if !(argCnt > 0 && argCnt <= 3) { @@ -148,16 +148,16 @@ func bcountCommand(c *client) error { } } - if cnt, err := c.db.BCount(args[0], start, end); err != nil { + if cnt, err := req.db.BCount(args[0], start, end); err != nil { return err } else { - c.resp.writeInteger(int64(cnt)) + req.resp.writeInteger(int64(cnt)) } return nil } -func boptCommand(c *client) error { - args := c.args +func boptCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } @@ -180,16 +180,16 @@ func boptCommand(c *client) error { return ErrCmdParams } - if blen, err := c.db.BOperation(op, dstKey, srcKeys...); err != nil { + if blen, err := req.db.BOperation(op, dstKey, srcKeys...); err != nil { return err } else { - c.resp.writeInteger(int64(blen)) + req.resp.writeInteger(int64(blen)) } return nil } -func bexpireCommand(c *client) error { - args := c.args +func bexpireCommand(req *requestContext) error { + args := req.args if len(args) == 0 { return ErrCmdParams } @@ -199,17 +199,17 @@ func bexpireCommand(c *client) error { return err } - if v, err := c.db.BExpire(args[0], duration); err != nil { + if v, err := req.db.BExpire(args[0], duration); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func bexpireatCommand(c *client) error { - args := c.args +func bexpireatCommand(req *requestContext) error { + args := req.args if len(args) == 0 { return ErrCmdParams } @@ -219,40 +219,40 @@ func bexpireatCommand(c *client) error { return err } - if v, err := c.db.BExpireAt(args[0], when); err != nil { + if v, err := req.db.BExpireAt(args[0], when); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func bttlCommand(c *client) error { - args := c.args +func bttlCommand(req *requestContext) error { + args := req.args if len(args) == 0 { return ErrCmdParams } - if v, err := c.db.BTTL(args[0]); err != nil { + if v, err := req.db.BTTL(args[0]); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func bpersistCommand(c *client) error { - args := c.args +func bpersistCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.BPersist(args[0]); err != nil { + if n, err := req.db.BPersist(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil diff --git a/server/cmd_hash.go b/server/cmd_hash.go index 0a736fc..e6dc433 100644 --- a/server/cmd_hash.go +++ b/server/cmd_hash.go @@ -4,87 +4,87 @@ import ( "github.com/siddontang/ledisdb/ledis" ) -func hsetCommand(c *client) error { - args := c.args +func hsetCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } - if n, err := c.db.HSet(args[0], args[1], args[2]); err != nil { + if n, err := req.db.HSet(args[0], args[1], args[2]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hgetCommand(c *client) error { - args := c.args +func hgetCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if v, err := c.db.HGet(args[0], args[1]); err != nil { + if v, err := req.db.HGet(args[0], args[1]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func hexistsCommand(c *client) error { - args := c.args +func hexistsCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } var n int64 = 1 - if v, err := c.db.HGet(args[0], args[1]); err != nil { + if v, err := req.db.HGet(args[0], args[1]); err != nil { return err } else { if v == nil { n = 0 } - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hdelCommand(c *client) error { - args := c.args +func hdelCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } - if n, err := c.db.HDel(args[0], args[1:]...); err != nil { + if n, err := req.db.HDel(args[0], args[1:]...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hlenCommand(c *client) error { - args := c.args +func hlenCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.HLen(args[0]); err != nil { + if n, err := req.db.HLen(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hincrbyCommand(c *client) error { - args := c.args +func hincrbyCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -95,16 +95,16 @@ func hincrbyCommand(c *client) error { } var n int64 - if n, err = c.db.HIncrBy(args[0], args[1], delta); err != nil { + if n, err = req.db.HIncrBy(args[0], args[1], delta); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hmsetCommand(c *client) error { - args := c.args +func hmsetCommand(req *requestContext) error { + args := req.args if len(args) < 3 { return ErrCmdParams } @@ -123,107 +123,107 @@ func hmsetCommand(c *client) error { kvs[i].Value = args[2*i+1] } - if err := c.db.HMset(key, kvs...); err != nil { + if err := req.db.HMset(key, kvs...); err != nil { return err } else { - c.resp.writeStatus(OK) + req.resp.writeStatus(OK) } return nil } -func hmgetCommand(c *client) error { - args := c.args +func hmgetCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } - if v, err := c.db.HMget(args[0], args[1:]...); err != nil { + if v, err := req.db.HMget(args[0], args[1:]...); err != nil { return err } else { - c.resp.writeSliceArray(v) + req.resp.writeSliceArray(v) } return nil } -func hgetallCommand(c *client) error { - args := c.args +func hgetallCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.HGetAll(args[0]); err != nil { + if v, err := req.db.HGetAll(args[0]); err != nil { return err } else { - c.resp.writeFVPairArray(v) + req.resp.writeFVPairArray(v) } return nil } -func hkeysCommand(c *client) error { - args := c.args +func hkeysCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.HKeys(args[0]); err != nil { + if v, err := req.db.HKeys(args[0]); err != nil { return err } else { - c.resp.writeSliceArray(v) + req.resp.writeSliceArray(v) } return nil } -func hvalsCommand(c *client) error { - args := c.args +func hvalsCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.HValues(args[0]); err != nil { + if v, err := req.db.HValues(args[0]); err != nil { return err } else { - c.resp.writeSliceArray(v) + req.resp.writeSliceArray(v) } return nil } -func hclearCommand(c *client) error { - args := c.args +func hclearCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.HClear(args[0]); err != nil { + if n, err := req.db.HClear(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hmclearCommand(c *client) error { - args := c.args +func hmclearCommand(req *requestContext) error { + args := req.args if len(args) < 1 { return ErrCmdParams } - if n, err := c.db.HMclear(args...); err != nil { + if n, err := req.db.HMclear(args...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func hexpireCommand(c *client) error { - args := c.args +func hexpireCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -233,17 +233,17 @@ func hexpireCommand(c *client) error { return err } - if v, err := c.db.HExpire(args[0], duration); err != nil { + if v, err := req.db.HExpire(args[0], duration); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func hexpireAtCommand(c *client) error { - args := c.args +func hexpireAtCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -253,40 +253,40 @@ func hexpireAtCommand(c *client) error { return err } - if v, err := c.db.HExpireAt(args[0], when); err != nil { + if v, err := req.db.HExpireAt(args[0], when); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func httlCommand(c *client) error { - args := c.args +func httlCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.HTTL(args[0]); err != nil { + if v, err := req.db.HTTL(args[0]); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func hpersistCommand(c *client) error { - args := c.args +func hpersistCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.HPersist(args[0]); err != nil { + if n, err := req.db.HPersist(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil diff --git a/server/cmd_kv.go b/server/cmd_kv.go index 4462c83..9ac922c 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -4,112 +4,112 @@ import ( "github.com/siddontang/ledisdb/ledis" ) -func getCommand(c *client) error { - args := c.args +func getCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.Get(args[0]); err != nil { + if v, err := req.db.Get(args[0]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func setCommand(c *client) error { - args := c.args +func setCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if err := c.db.Set(args[0], args[1]); err != nil { + if err := req.db.Set(args[0], args[1]); err != nil { return err } else { - c.resp.writeStatus(OK) + req.resp.writeStatus(OK) } return nil } -func getsetCommand(c *client) error { - args := c.args +func getsetCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if v, err := c.db.GetSet(args[0], args[1]); err != nil { + if v, err := req.db.GetSet(args[0], args[1]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func setnxCommand(c *client) error { - args := c.args +func setnxCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if n, err := c.db.SetNX(args[0], args[1]); err != nil { + if n, err := req.db.SetNX(args[0], args[1]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func existsCommand(c *client) error { - args := c.args +func existsCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.Exists(args[0]); err != nil { + if n, err := req.db.Exists(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func incrCommand(c *client) error { - args := c.args +func incrCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.Incr(c.args[0]); err != nil { + if n, err := req.db.Incr(req.args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func decrCommand(c *client) error { - args := c.args +func decrCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.Decr(c.args[0]); err != nil { + if n, err := req.db.Decr(req.args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func incrbyCommand(c *client) error { - args := c.args +func incrbyCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -119,17 +119,17 @@ func incrbyCommand(c *client) error { return err } - if n, err := c.db.IncryBy(c.args[0], delta); err != nil { + if n, err := req.db.IncryBy(req.args[0], delta); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func decrbyCommand(c *client) error { - args := c.args +func decrbyCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -139,32 +139,32 @@ func decrbyCommand(c *client) error { return err } - if n, err := c.db.DecrBy(c.args[0], delta); err != nil { + if n, err := req.db.DecrBy(req.args[0], delta); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func delCommand(c *client) error { - args := c.args +func delCommand(req *requestContext) error { + args := req.args if len(args) == 0 { return ErrCmdParams } - if n, err := c.db.Del(args...); err != nil { + if n, err := req.db.Del(args...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func msetCommand(c *client) error { - args := c.args +func msetCommand(req *requestContext) error { + args := req.args if len(args) == 0 || len(args)%2 != 0 { return ErrCmdParams } @@ -175,36 +175,36 @@ func msetCommand(c *client) error { kvs[i].Value = args[2*i+1] } - if err := c.db.MSet(kvs...); err != nil { + if err := req.db.MSet(kvs...); err != nil { return err } else { - c.resp.writeStatus(OK) + req.resp.writeStatus(OK) } return nil } -// func setexCommand(c *client) error { +// func setexCommand(req *requestContext) error { // return nil // } -func mgetCommand(c *client) error { - args := c.args +func mgetCommand(req *requestContext) error { + args := req.args if len(args) == 0 { return ErrCmdParams } - if v, err := c.db.MGet(args...); err != nil { + if v, err := req.db.MGet(args...); err != nil { return err } else { - c.resp.writeSliceArray(v) + req.resp.writeSliceArray(v) } return nil } -func expireCommand(c *client) error { - args := c.args +func expireCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -214,17 +214,17 @@ func expireCommand(c *client) error { return err } - if v, err := c.db.Expire(args[0], duration); err != nil { + if v, err := req.db.Expire(args[0], duration); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func expireAtCommand(c *client) error { - args := c.args +func expireAtCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -234,40 +234,40 @@ func expireAtCommand(c *client) error { return err } - if v, err := c.db.ExpireAt(args[0], when); err != nil { + if v, err := req.db.ExpireAt(args[0], when); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func ttlCommand(c *client) error { - args := c.args +func ttlCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.TTL(args[0]); err != nil { + if v, err := req.db.TTL(args[0]); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func persistCommand(c *client) error { - args := c.args +func persistCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.Persist(args[0]); err != nil { + if n, err := req.db.Persist(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil diff --git a/server/cmd_list.go b/server/cmd_list.go index a4e971b..dd9f6ef 100644 --- a/server/cmd_list.go +++ b/server/cmd_list.go @@ -4,83 +4,83 @@ import ( "github.com/siddontang/ledisdb/ledis" ) -func lpushCommand(c *client) error { - args := c.args +func lpushCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } - if n, err := c.db.LPush(args[0], args[1:]...); err != nil { + if n, err := req.db.LPush(args[0], args[1:]...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func rpushCommand(c *client) error { - args := c.args +func rpushCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } - if n, err := c.db.RPush(args[0], args[1:]...); err != nil { + if n, err := req.db.RPush(args[0], args[1:]...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func lpopCommand(c *client) error { - args := c.args +func lpopCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.LPop(args[0]); err != nil { + if v, err := req.db.LPop(args[0]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func rpopCommand(c *client) error { - args := c.args +func rpopCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.RPop(args[0]); err != nil { + if v, err := req.db.RPop(args[0]); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func llenCommand(c *client) error { - args := c.args +func llenCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.LLen(args[0]); err != nil { + if n, err := req.db.LLen(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func lindexCommand(c *client) error { - args := c.args +func lindexCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -90,17 +90,17 @@ func lindexCommand(c *client) error { return err } - if v, err := c.db.LIndex(args[0], int32(index)); err != nil { + if v, err := req.db.LIndex(args[0], int32(index)); err != nil { return err } else { - c.resp.writeBulk(v) + req.resp.writeBulk(v) } return nil } -func lrangeCommand(c *client) error { - args := c.args +func lrangeCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -119,47 +119,47 @@ func lrangeCommand(c *client) error { return err } - if v, err := c.db.LRange(args[0], int32(start), int32(stop)); err != nil { + if v, err := req.db.LRange(args[0], int32(start), int32(stop)); err != nil { return err } else { - c.resp.writeSliceArray(v) + req.resp.writeSliceArray(v) } return nil } -func lclearCommand(c *client) error { - args := c.args +func lclearCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.LClear(args[0]); err != nil { + if n, err := req.db.LClear(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func lmclearCommand(c *client) error { - args := c.args +func lmclearCommand(req *requestContext) error { + args := req.args if len(args) < 1 { return ErrCmdParams } - if n, err := c.db.LMclear(args...); err != nil { + if n, err := req.db.LMclear(args...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func lexpireCommand(c *client) error { - args := c.args +func lexpireCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -169,17 +169,17 @@ func lexpireCommand(c *client) error { return err } - if v, err := c.db.LExpire(args[0], duration); err != nil { + if v, err := req.db.LExpire(args[0], duration); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func lexpireAtCommand(c *client) error { - args := c.args +func lexpireAtCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -189,40 +189,40 @@ func lexpireAtCommand(c *client) error { return err } - if v, err := c.db.LExpireAt(args[0], when); err != nil { + if v, err := req.db.LExpireAt(args[0], when); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func lttlCommand(c *client) error { - args := c.args +func lttlCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.LTTL(args[0]); err != nil { + if v, err := req.db.LTTL(args[0]); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func lpersistCommand(c *client) error { - args := c.args +func lpersistCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.LPersist(args[0]); err != nil { + if n, err := req.db.LPersist(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil diff --git a/server/cmd_replication.go b/server/cmd_replication.go index fe84191..85c0861 100644 --- a/server/cmd_replication.go +++ b/server/cmd_replication.go @@ -11,8 +11,8 @@ import ( "strings" ) -func slaveofCommand(c *client) error { - args := c.args +func slaveofCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams @@ -31,23 +31,23 @@ func slaveofCommand(c *client) error { masterAddr = fmt.Sprintf("%s:%s", args[0], args[1]) } - if err := c.app.slaveof(masterAddr); err != nil { + if err := req.app.slaveof(masterAddr); err != nil { return err } - c.resp.writeStatus(OK) + req.resp.writeStatus(OK) return nil } -func fullsyncCommand(c *client) error { +func fullsyncCommand(req *requestContext) error { //todo, multi fullsync may use same dump file - dumpFile, err := ioutil.TempFile(c.app.cfg.DataDir, "dump_") + dumpFile, err := ioutil.TempFile(req.app.cfg.DataDir, "dump_") if err != nil { return err } - if err = c.app.ldb.Dump(dumpFile); err != nil { + if err = req.app.ldb.Dump(dumpFile); err != nil { return err } @@ -56,7 +56,7 @@ func fullsyncCommand(c *client) error { dumpFile.Seek(0, os.SEEK_SET) - c.resp.writeBulkFrom(n, dumpFile) + req.resp.writeBulkFrom(n, dumpFile) name := dumpFile.Name() dumpFile.Close() @@ -68,8 +68,8 @@ func fullsyncCommand(c *client) error { var reserveInfoSpace = make([]byte, 16) -func syncCommand(c *client) error { - args := c.args +func syncCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -87,32 +87,32 @@ func syncCommand(c *client) error { return ErrCmdParams } - c.syncBuf.Reset() + req.syncBuf.Reset() //reserve space to write master info - if _, err := c.syncBuf.Write(reserveInfoSpace); err != nil { + if _, err := req.syncBuf.Write(reserveInfoSpace); err != nil { return err } m := &ledis.MasterInfo{logIndex, logPos} - if _, err := c.app.ldb.ReadEventsTo(m, &c.syncBuf); err != nil { + if _, err := req.app.ldb.ReadEventsTo(m, &req.syncBuf); err != nil { return err } else { - buf := c.syncBuf.Bytes() + buf := req.syncBuf.Bytes() binary.BigEndian.PutUint64(buf[0:], uint64(m.LogFileIndex)) binary.BigEndian.PutUint64(buf[8:], uint64(m.LogPos)) - if len(c.compressBuf) < snappy.MaxEncodedLen(len(buf)) { - c.compressBuf = make([]byte, snappy.MaxEncodedLen(len(buf))) + if len(req.compressBuf) < snappy.MaxEncodedLen(len(buf)) { + req.compressBuf = make([]byte, snappy.MaxEncodedLen(len(buf))) } - if buf, err = snappy.Encode(c.compressBuf, buf); err != nil { + if buf, err = snappy.Encode(req.compressBuf, buf); err != nil { return err } - c.resp.writeBulk(buf) + req.resp.writeBulk(buf) } return nil diff --git a/server/cmd_zset.go b/server/cmd_zset.go index b697453..d2facd3 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -12,8 +12,8 @@ import ( var errScoreOverflow = errors.New("zset score overflow") -func zaddCommand(c *client) error { - args := c.args +func zaddCommand(req *requestContext) error { + args := req.args if len(args) < 3 { return ErrCmdParams } @@ -36,66 +36,66 @@ func zaddCommand(c *client) error { params[i].Member = args[2*i+1] } - if n, err := c.db.ZAdd(key, params...); err != nil { + if n, err := req.db.ZAdd(key, params...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zcardCommand(c *client) error { - args := c.args +func zcardCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.ZCard(args[0]); err != nil { + if n, err := req.db.ZCard(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zscoreCommand(c *client) error { - args := c.args +func zscoreCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if s, err := c.db.ZScore(args[0], args[1]); err != nil { + if s, err := req.db.ZScore(args[0], args[1]); err != nil { if err == ledis.ErrScoreMiss { - c.resp.writeBulk(nil) + req.resp.writeBulk(nil) } else { return err } } else { - c.resp.writeBulk(ledis.StrPutInt64(s)) + req.resp.writeBulk(ledis.StrPutInt64(s)) } return nil } -func zremCommand(c *client) error { - args := c.args +func zremCommand(req *requestContext) error { + args := req.args if len(args) < 2 { return ErrCmdParams } - if n, err := c.db.ZRem(args[0], args[1:]...); err != nil { + if n, err := req.db.ZRem(args[0], args[1:]...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zincrbyCommand(c *client) error { - args := c.args +func zincrbyCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -107,10 +107,10 @@ func zincrbyCommand(c *client) error { return err } - if v, err := c.db.ZIncrBy(key, delta, args[2]); err != nil { + if v, err := req.db.ZIncrBy(key, delta, args[2]); err != nil { return err } else { - c.resp.writeBulk(ledis.StrPutInt64(v)) + req.resp.writeBulk(ledis.StrPutInt64(v)) } return nil @@ -178,8 +178,8 @@ func zparseScoreRange(minBuf []byte, maxBuf []byte) (min int64, max int64, err e return } -func zcountCommand(c *client) error { - args := c.args +func zcountCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -190,77 +190,77 @@ func zcountCommand(c *client) error { } if min > max { - c.resp.writeInteger(0) + req.resp.writeInteger(0) return nil } - if n, err := c.db.ZCount(args[0], min, max); err != nil { + if n, err := req.db.ZCount(args[0], min, max); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zrankCommand(c *client) error { - args := c.args +func zrankCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if n, err := c.db.ZRank(args[0], args[1]); err != nil { + if n, err := req.db.ZRank(args[0], args[1]); err != nil { return err } else if n == -1 { - c.resp.writeBulk(nil) + req.resp.writeBulk(nil) } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zrevrankCommand(c *client) error { - args := c.args +func zrevrankCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } - if n, err := c.db.ZRevRank(args[0], args[1]); err != nil { + if n, err := req.db.ZRevRank(args[0], args[1]); err != nil { return err } else if n == -1 { - c.resp.writeBulk(nil) + req.resp.writeBulk(nil) } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zremrangebyrankCommand(c *client) error { - args := c.args +func zremrangebyrankCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } key := args[0] - start, stop, err := zparseRange(c, args[1], args[2]) + start, stop, err := zparseRange(req, args[1], args[2]) if err != nil { return err } - if n, err := c.db.ZRemRangeByRank(key, start, stop); err != nil { + if n, err := req.db.ZRemRangeByRank(key, start, stop); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zremrangebyscoreCommand(c *client) error { - args := c.args +func zremrangebyscoreCommand(req *requestContext) error { + args := req.args if len(args) != 3 { return ErrCmdParams } @@ -271,16 +271,16 @@ func zremrangebyscoreCommand(c *client) error { return err } - if n, err := c.db.ZRemRangeByScore(key, min, max); err != nil { + if n, err := req.db.ZRemRangeByScore(key, min, max); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zparseRange(c *client, a1 []byte, a2 []byte) (start int, stop int, err error) { +func zparseRange(req *requestContext, a1 []byte, a2 []byte) (start int, stop int, err error) { if start, err = strconv.Atoi(ledis.String(a1)); err != nil { return } @@ -292,15 +292,15 @@ func zparseRange(c *client, a1 []byte, a2 []byte) (start int, stop int, err erro return } -func zrangeGeneric(c *client, reverse bool) error { - args := c.args +func zrangeGeneric(req *requestContext, reverse bool) error { + args := req.args if len(args) < 3 { return ErrCmdParams } key := args[0] - start, stop, err := zparseRange(c, args[1], args[2]) + start, stop, err := zparseRange(req, args[1], args[2]) if err != nil { return err } @@ -312,24 +312,24 @@ func zrangeGeneric(c *client, reverse bool) error { withScores = true } - if datas, err := c.db.ZRangeGeneric(key, start, stop, reverse); err != nil { + if datas, err := req.db.ZRangeGeneric(key, start, stop, reverse); err != nil { return err } else { - c.resp.writeScorePairArray(datas, withScores) + req.resp.writeScorePairArray(datas, withScores) } return nil } -func zrangeCommand(c *client) error { - return zrangeGeneric(c, false) +func zrangeCommand(req *requestContext) error { + return zrangeGeneric(req, false) } -func zrevrangeCommand(c *client) error { - return zrangeGeneric(c, true) +func zrevrangeCommand(req *requestContext) error { + return zrangeGeneric(req, true) } -func zrangebyscoreGeneric(c *client, reverse bool) error { - args := c.args +func zrangebyscoreGeneric(req *requestContext, reverse bool) error { + args := req.args if len(args) < 3 { return ErrCmdParams } @@ -383,59 +383,59 @@ func zrangebyscoreGeneric(c *client, reverse bool) error { if offset < 0 { //for ledis, if offset < 0, a empty will return //so here we directly return a empty array - c.resp.writeArray([]interface{}{}) + req.resp.writeArray([]interface{}{}) return nil } - if datas, err := c.db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { + if datas, err := req.db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { return err } else { - c.resp.writeScorePairArray(datas, withScores) + req.resp.writeScorePairArray(datas, withScores) } return nil } -func zrangebyscoreCommand(c *client) error { - return zrangebyscoreGeneric(c, false) +func zrangebyscoreCommand(req *requestContext) error { + return zrangebyscoreGeneric(req, false) } -func zrevrangebyscoreCommand(c *client) error { - return zrangebyscoreGeneric(c, true) +func zrevrangebyscoreCommand(req *requestContext) error { + return zrangebyscoreGeneric(req, true) } -func zclearCommand(c *client) error { - args := c.args +func zclearCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.ZClear(args[0]); err != nil { + if n, err := req.db.ZClear(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zmclearCommand(c *client) error { - args := c.args +func zmclearCommand(req *requestContext) error { + args := req.args if len(args) < 1 { return ErrCmdParams } - if n, err := c.db.ZMclear(args...); err != nil { + if n, err := req.db.ZMclear(args...); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil } -func zexpireCommand(c *client) error { - args := c.args +func zexpireCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -445,17 +445,17 @@ func zexpireCommand(c *client) error { return err } - if v, err := c.db.ZExpire(args[0], duration); err != nil { + if v, err := req.db.ZExpire(args[0], duration); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func zexpireAtCommand(c *client) error { - args := c.args +func zexpireAtCommand(req *requestContext) error { + args := req.args if len(args) != 2 { return ErrCmdParams } @@ -465,40 +465,40 @@ func zexpireAtCommand(c *client) error { return err } - if v, err := c.db.ZExpireAt(args[0], when); err != nil { + if v, err := req.db.ZExpireAt(args[0], when); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func zttlCommand(c *client) error { - args := c.args +func zttlCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if v, err := c.db.ZTTL(args[0]); err != nil { + if v, err := req.db.ZTTL(args[0]); err != nil { return err } else { - c.resp.writeInteger(v) + req.resp.writeInteger(v) } return nil } -func zpersistCommand(c *client) error { - args := c.args +func zpersistCommand(req *requestContext) error { + args := req.args if len(args) != 1 { return ErrCmdParams } - if n, err := c.db.ZPersist(args[0]); err != nil { + if n, err := req.db.ZPersist(args[0]); err != nil { return err } else { - c.resp.writeInteger(n) + req.resp.writeInteger(n) } return nil diff --git a/server/command.go b/server/command.go index 23ca7bd..440a177 100644 --- a/server/command.go +++ b/server/command.go @@ -8,7 +8,7 @@ import ( "strings" ) -type CommandFunc func(c *client) error +type CommandFunc func(req *requestContext) error var regCmds = map[string]CommandFunc{} @@ -20,33 +20,33 @@ func register(name string, f CommandFunc) { regCmds[name] = f } -func pingCommand(c *client) error { - c.resp.writeStatus(PONG) +func pingCommand(req *requestContext) error { + req.resp.writeStatus(PONG) return nil } -func echoCommand(c *client) error { - if len(c.args) != 1 { +func echoCommand(req *requestContext) error { + if len(req.args) != 1 { return ErrCmdParams } - c.resp.writeBulk(c.args[0]) + req.resp.writeBulk(req.args[0]) return nil } -func selectCommand(c *client) error { - if len(c.args) != 1 { +func selectCommand(req *requestContext) error { + if len(req.args) != 1 { return ErrCmdParams } - if index, err := strconv.Atoi(ledis.String(c.args[0])); err != nil { + if index, err := strconv.Atoi(ledis.String(req.args[0])); err != nil { return err } else { - if db, err := c.ldb.Select(index); err != nil { + if db, err := req.ldb.Select(index); err != nil { return err } else { - c.db = db - c.resp.writeStatus(OK) + req.db = db + req.resp.writeStatus(OK) } } return nil diff --git a/server/request.go b/server/request.go new file mode 100644 index 0000000..5d6dacf --- /dev/null +++ b/server/request.go @@ -0,0 +1,200 @@ +package server + +import ( + "bytes" + "github.com/siddontang/go-log/log" + "github.com/siddontang/ledisdb/ledis" + "io" + "runtime" + "sync" + "time" +) + +type responseWriter interface { + writeError(error) + writeStatus(string) + writeInteger(int64) + writeBulk([]byte) + writeArray([]interface{}) + writeSliceArray([][]byte) + writeFVPairArray([]ledis.FVPair) + writeScorePairArray([]ledis.ScorePair, bool) + writeBulkFrom(int64, io.Reader) + flush() +} + +type requestContext struct { + app *App + ldb *ledis.Ledis + db *ledis.DB + + remoteAddr string + cmd string + args [][]byte + + resp responseWriter + + syncBuf bytes.Buffer + compressBuf []byte + + finish chan interface{} +} + +type requestHandler struct { + app *App + + async bool + quit chan struct{} + jobs *sync.WaitGroup + + reqs chan *requestContext + reqErr chan error + + buf bytes.Buffer +} + +func newRequestContext(app *App) *requestContext { + req := new(requestContext) + + req.app = app + req.ldb = app.ldb + req.db, _ = app.ldb.Select(0) //use default db + + req.compressBuf = make([]byte, 256) + req.finish = make(chan interface{}, 1) + + return req +} + +func newRequestHandler(app *App) *requestHandler { + hdl := new(requestHandler) + + hdl.app = app + + hdl.async = false + hdl.jobs = new(sync.WaitGroup) + hdl.quit = make(chan struct{}) + + hdl.reqs = make(chan *requestContext) + hdl.reqErr = make(chan error) + + return hdl +} + +func (h *requestHandler) asyncRun() { + if !h.async { + // todo ... not safe + h.async = true + go h.run() + } +} + +func (h *requestHandler) close() { + if h.async { + close(h.quit) + h.jobs.Wait() + } +} + +func (h *requestHandler) run() { + defer func() { + if e := recover(); e != nil { + buf := make([]byte, 4096) + n := runtime.Stack(buf, false) + buf = buf[0:n] + + log.Fatal("request handler run panic %s:%v", buf, e) + } + }() + + h.jobs.Add(1) + + var req *requestContext + for !h.async { + select { + case req = <-h.reqs: + if req != nil { + h.performance(req) + } + case <-h.quit: + h.async = true + break + } + } + + h.jobs.Done() + return +} + +func (h *requestHandler) postRequest(req *requestContext) { + if h.async { + h.reqs <- req + } else { + h.performance(req) + } + + <-req.finish +} + +func (h *requestHandler) performance(req *requestContext) { + var err error + + start := time.Now() + + if len(req.cmd) == 0 { + err = ErrEmptyCommand + } else if exeCmd, ok := regCmds[req.cmd]; !ok { + err = ErrNotFound + } else { + go func() { + h.reqErr <- exeCmd(req) + }() + + err = <-h.reqErr + } + + duration := time.Since(start) + + if h.app.access != nil { + fullCmd := h.catGenericCommand(req) + cost := duration.Nanoseconds() / 1000000 + + h.app.access.Log(req.remoteAddr, cost, fullCmd[:256], err) + } + + if err != nil { + req.resp.writeError(err) + } + req.resp.flush() + + req.finish <- nil + return +} + +// func (h *requestHandler) catFullCommand(req *requestContext) []byte { +// +// // if strings.HasSuffix(cmd, "expire") { +// // catExpireCommand(c, buffer) +// // } else { +// // catGenericCommand(c, buffer) +// // } +// +// return h.catGenericCommand(req) +// } + +func (h *requestHandler) catGenericCommand(req *requestContext) []byte { + buffer := h.buf + buffer.Reset() + + buffer.Write([]byte(req.cmd)) + + nargs := len(req.args) + for i, arg := range req.args { + buffer.Write(arg) + if i != nargs-1 { + buffer.WriteByte(' ') + } + } + + return buffer.Bytes() +} From d0e7698984c81c30060e2e66f73098b761365b1e Mon Sep 17 00:00:00 2001 From: silentsai Date: Fri, 1 Aug 2014 09:38:08 +0800 Subject: [PATCH 22/51] cut async feature from request handler --- server/client_resp.go | 12 ++++--- server/request.go | 75 +------------------------------------------ 2 files changed, 9 insertions(+), 78 deletions(-) diff --git a/server/client_resp.go b/server/client_resp.go index 4d0110a..e7a4114 100644 --- a/server/client_resp.go +++ b/server/client_resp.go @@ -42,6 +42,7 @@ func newClientRESP(conn net.Conn, app *App) { c.req = newRequestContext(app) c.req.resp = newWriterRESP(conn) + c.req.remoteAddr = conn.RemoteAddr().String() c.hdl = newRequestHandler(app) @@ -133,9 +134,6 @@ func (c *respClient) readRequest() ([][]byte, error) { func (c *respClient) handleRequest(reqData [][]byte) { req := c.req - req.db = c.db - req.remoteAddr = c.conn.RemoteAddr().String() - if len(reqData) == 0 { c.req.cmd = "" c.req.args = reqData[0:0] @@ -144,7 +142,13 @@ func (c *respClient) handleRequest(reqData [][]byte) { c.req.args = reqData[1:] } - c.hdl.postRequest(req) + req.db = c.db + + c.hdl.handle(req) + + c.db = req.db // "SELECT" + + return } // response writer diff --git a/server/request.go b/server/request.go index 5d6dacf..f74d162 100644 --- a/server/request.go +++ b/server/request.go @@ -2,11 +2,8 @@ package server import ( "bytes" - "github.com/siddontang/go-log/log" "github.com/siddontang/ledisdb/ledis" "io" - "runtime" - "sync" "time" ) @@ -36,18 +33,11 @@ type requestContext struct { syncBuf bytes.Buffer compressBuf []byte - - finish chan interface{} } type requestHandler struct { app *App - async bool - quit chan struct{} - jobs *sync.WaitGroup - - reqs chan *requestContext reqErr chan error buf bytes.Buffer @@ -61,7 +51,6 @@ func newRequestContext(app *App) *requestContext { req.db, _ = app.ldb.Select(0) //use default db req.compressBuf = make([]byte, 256) - req.finish = make(chan interface{}, 1) return req } @@ -70,73 +59,12 @@ func newRequestHandler(app *App) *requestHandler { hdl := new(requestHandler) hdl.app = app - - hdl.async = false - hdl.jobs = new(sync.WaitGroup) - hdl.quit = make(chan struct{}) - - hdl.reqs = make(chan *requestContext) hdl.reqErr = make(chan error) return hdl } -func (h *requestHandler) asyncRun() { - if !h.async { - // todo ... not safe - h.async = true - go h.run() - } -} - -func (h *requestHandler) close() { - if h.async { - close(h.quit) - h.jobs.Wait() - } -} - -func (h *requestHandler) run() { - defer func() { - if e := recover(); e != nil { - buf := make([]byte, 4096) - n := runtime.Stack(buf, false) - buf = buf[0:n] - - log.Fatal("request handler run panic %s:%v", buf, e) - } - }() - - h.jobs.Add(1) - - var req *requestContext - for !h.async { - select { - case req = <-h.reqs: - if req != nil { - h.performance(req) - } - case <-h.quit: - h.async = true - break - } - } - - h.jobs.Done() - return -} - -func (h *requestHandler) postRequest(req *requestContext) { - if h.async { - h.reqs <- req - } else { - h.performance(req) - } - - <-req.finish -} - -func (h *requestHandler) performance(req *requestContext) { +func (h *requestHandler) handle(req *requestContext) { var err error start := time.Now() @@ -167,7 +95,6 @@ func (h *requestHandler) performance(req *requestContext) { } req.resp.flush() - req.finish <- nil return } From e46554e9f67bfb261073b3dc4fa3c551765008a1 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 11:42:16 +0800 Subject: [PATCH 23/51] refactor http interface --- server/app.go | 5 +- server/client_http.go | 254 ++++++++++++++++++++++++++++++++++++++++++ server/client_resp.go | 5 +- server/request.go | 36 +++--- 4 files changed, 272 insertions(+), 28 deletions(-) create mode 100644 server/client_http.go diff --git a/server/app.go b/server/app.go index 781e097..4cb4b05 100644 --- a/server/app.go +++ b/server/app.go @@ -3,7 +3,6 @@ package server import ( "fmt" "github.com/siddontang/ledisdb/ledis" - . "github.com/siddontang/ledisdb/server/http" "net" "net/http" "path" @@ -134,7 +133,9 @@ func (app *App) httpServe() { mux := http.NewServeMux() - mux.Handle("/", &CmdHandler{app.Ledis()}) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + newClientHTTP(app, w, r) + }) svr := http.Server{Handler: mux} svr.Serve(app.httpListener) diff --git a/server/client_http.go b/server/client_http.go new file mode 100644 index 0000000..a2ae729 --- /dev/null +++ b/server/client_http.go @@ -0,0 +1,254 @@ +package server + +import ( + "fmt" + "github.com/siddontang/go-log/log" + "github.com/siddontang/ledisdb/ledis" + "io" + "net/http" + "strconv" + "strings" + + "encoding/json" + "github.com/ugorji/go/codec" + "gopkg.in/mgo.v2/bson" +) + +var allowedContentTypes = map[string]struct{}{ + "json": struct{}{}, + "bson": struct{}{}, + "msgpack": struct{}{}, +} + +type httpClient struct { + app *App + db *ledis.DB + ldb *ledis.Ledis + + resp responseWriter + req *requestContext +} + +type httpWriter struct { + contentType string + cmd string + w http.ResponseWriter +} + +// http context + +func newClientHTTP(app *App, w http.ResponseWriter, r *http.Request) { + var err error + c := new(httpClient) + c.app = app + c.ldb = app.ldb + c.db, err = c.ldb.Select(0) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + c.req, err = c.makeRequest(app, r, w) + if err != nil { + w.Write([]byte(err.Error())) + return + } + c.req.perform() +} + +func (c *httpClient) addr(r *http.Request) string { + addr := r.Header.Get("X-Forwarded-For") + if addr == "" { + addr = r.Header.Get("X-Real-IP") + if addr == "" { + addr = r.Header.Get("Remote-Addr") + } + } + return addr +} + +func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWriter) (*requestContext, error) { + var err error + + db, cmd, argsStr, contentType := c.parseReqPath(r) + + c.db, err = app.ldb.Select(db) + if err != nil { + return nil, err + } + + contentType = strings.ToLower(contentType) + + if _, ok := allowedContentTypes[contentType]; !ok { + return nil, fmt.Errorf("unsupported content type: '%s', only json, bson, msgpack are supported", contentType) + } + + req := newRequestContext(app) + args := make([][]byte, len(argsStr)) + for i, arg := range argsStr { + args[i] = []byte(arg) + } + + req.cmd = cmd + req.args = args + req.remoteAddr = c.addr(r) + req.resp = &httpWriter{contentType, cmd, w} + return req, nil +} + +func (c *httpClient) parseReqPath(r *http.Request) (db int, cmd string, args []string, contentType string) { + + contentType = r.FormValue("type") + if contentType == "" { + contentType = "json" + } + + substrings := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/") + if len(substrings) == 1 { + return 0, substrings[0], substrings[1:], contentType + } + db, err := strconv.Atoi(substrings[0]) + if err != nil { + cmd = substrings[0] + args = substrings[1:] + } else { + cmd = substrings[1] + args = substrings[2:] + } + + return +} + +// http writer + +func (w *httpWriter) genericWrite(result interface{}) { + + m := map[string]interface{}{ + w.cmd: result, + } + switch w.contentType { + case "json": + writeJSON(&m, w.w) + case "bson": + writeBSON(&m, w.w) + case "msgpack": + writeMsgPack(&m, w.w) + default: + log.Error("invalid content type %s", w.contentType) + } +} + +func (w *httpWriter) writeError(err error) { + result := [2]interface{}{ + false, + fmt.Sprintf("ERR %s", err.Error()), + } + w.genericWrite(result) +} + +func (w *httpWriter) writeStatus(status string) { + w.genericWrite(status) +} + +func (w *httpWriter) writeInteger(n int64) { + w.genericWrite(n) +} + +func (w *httpWriter) writeBulk(b []byte) { + if b == nil { + w.genericWrite(nil) + } else { + w.genericWrite(ledis.String(b)) + } +} + +func (w *httpWriter) writeArray(lst []interface{}) { + w.genericWrite(lst) +} + +func (w *httpWriter) writeSliceArray(lst [][]byte) { + arr := make([]interface{}, len(lst)) + for i, elem := range lst { + if elem == nil { + arr[i] = nil + } else { + arr[i] = ledis.String(elem) + } + } + w.genericWrite(arr) +} + +func (w *httpWriter) writeFVPairArray(lst []ledis.FVPair) { + m := make(map[string]string) + for _, elem := range lst { + m[ledis.String(elem.Field)] = ledis.String(elem.Value) + } + w.genericWrite(m) +} + +func (w *httpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { + var arr []string + if withScores { + arr = make([]string, 2*len(lst)) + for i, data := range lst { + arr[2*i] = ledis.String(data.Member) + arr[2*i+1] = strconv.FormatInt(data.Score, 10) + } + } else { + arr = make([]string, len(lst)) + for i, data := range lst { + arr[i] = ledis.String(data.Member) + } + } + w.genericWrite(arr) +} + +func (w *httpWriter) writeBulkFrom(n int64, rb io.Reader) { + w.writeError(fmt.Errorf("unsuport")) +} + +func (w *httpWriter) flush() { + +} + +func writeJSON(resutl interface{}, w http.ResponseWriter) { + buf, err := json.Marshal(resutl) + if err != nil { + log.Error(err.Error()) + return + } + + w.Header().Set("Content-type", "application/json; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(len(buf))) + + _, err = w.Write(buf) + if err != nil { + log.Error(err.Error()) + } +} + +func writeBSON(result interface{}, w http.ResponseWriter) { + buf, err := bson.Marshal(result) + if err != nil { + log.Error(err.Error()) + return + } + + w.Header().Set("Content-type", "application/octet-stream") + w.Header().Set("Content-Length", strconv.Itoa(len(buf))) + + _, err = w.Write(buf) + if err != nil { + log.Error(err.Error()) + } +} + +func writeMsgPack(result interface{}, w http.ResponseWriter) { + w.Header().Set("Content-type", "application/octet-stream") + + var mh codec.MsgpackHandle + enc := codec.NewEncoder(w, &mh) + if err := enc.Encode(result); err != nil { + log.Error(err.Error()) + } +} diff --git a/server/client_resp.go b/server/client_resp.go index e7a4114..5252c6a 100644 --- a/server/client_resp.go +++ b/server/client_resp.go @@ -23,7 +23,6 @@ type respClient struct { rb *bufio.Reader req *requestContext - hdl *requestHandler } type respWriter struct { @@ -44,8 +43,6 @@ func newClientRESP(conn net.Conn, app *App) { c.req.resp = newWriterRESP(conn) c.req.remoteAddr = conn.RemoteAddr().String() - c.hdl = newRequestHandler(app) - go c.run() } @@ -144,7 +141,7 @@ func (c *respClient) handleRequest(reqData [][]byte) { req.db = c.db - c.hdl.handle(req) + c.req.perform() c.db = req.db // "SELECT" diff --git a/server/request.go b/server/request.go index f74d162..5c80284 100644 --- a/server/request.go +++ b/server/request.go @@ -33,10 +33,6 @@ type requestContext struct { syncBuf bytes.Buffer compressBuf []byte -} - -type requestHandler struct { - app *App reqErr chan error @@ -51,20 +47,12 @@ func newRequestContext(app *App) *requestContext { req.db, _ = app.ldb.Select(0) //use default db req.compressBuf = make([]byte, 256) + req.reqErr = make(chan error) return req } -func newRequestHandler(app *App) *requestHandler { - hdl := new(requestHandler) - - hdl.app = app - hdl.reqErr = make(chan error) - - return hdl -} - -func (h *requestHandler) handle(req *requestContext) { +func (req *requestContext) perform() { var err error start := time.Now() @@ -75,26 +63,30 @@ func (h *requestHandler) handle(req *requestContext) { err = ErrNotFound } else { go func() { - h.reqErr <- exeCmd(req) + req.reqErr <- exeCmd(req) }() - err = <-h.reqErr + err = <-req.reqErr } duration := time.Since(start) - if h.app.access != nil { - fullCmd := h.catGenericCommand(req) + if req.app.access != nil { + fullCmd := req.catGenericCommand() cost := duration.Nanoseconds() / 1000000 - h.app.access.Log(req.remoteAddr, cost, fullCmd[:256], err) + truncateLen := len(fullCmd) + if truncateLen > 256 { + truncateLen = 256 + } + + req.app.access.Log(req.remoteAddr, cost, fullCmd[:truncateLen], err) } if err != nil { req.resp.writeError(err) } req.resp.flush() - return } @@ -109,8 +101,8 @@ func (h *requestHandler) handle(req *requestContext) { // return h.catGenericCommand(req) // } -func (h *requestHandler) catGenericCommand(req *requestContext) []byte { - buffer := h.buf +func (req *requestContext) catGenericCommand() []byte { + buffer := req.buf buffer.Reset() buffer.Write([]byte(req.cmd)) From a88c8c46d83962851a2f3f1d50eddb184f222501 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 11:43:05 +0800 Subject: [PATCH 24/51] rm server/http --- server/http/base.go | 34 --- server/http/base_test.go | 40 --- server/http/cmd_bit.go | 228 --------------- server/http/cmd_bit_test.go | 274 ------------------ server/http/cmd_hash.go | 311 --------------------- server/http/cmd_hash_test.go | 369 ------------------------ server/http/cmd_kv.go | 278 ------------------ server/http/cmd_kv_test.go | 261 ----------------- server/http/cmd_list.go | 248 ----------------- server/http/cmd_list_test.go | 217 --------------- server/http/cmd_zset.go | 527 ----------------------------------- server/http/cmd_zset_test.go | 299 -------------------- server/http/handler.go | 156 ----------- server/http/http_io.go | 95 ------- server/http/readme.md | 42 --- 15 files changed, 3379 deletions(-) delete mode 100644 server/http/base.go delete mode 100644 server/http/base_test.go delete mode 100644 server/http/cmd_bit.go delete mode 100644 server/http/cmd_bit_test.go delete mode 100644 server/http/cmd_hash.go delete mode 100644 server/http/cmd_hash_test.go delete mode 100644 server/http/cmd_kv.go delete mode 100644 server/http/cmd_kv_test.go delete mode 100644 server/http/cmd_list.go delete mode 100644 server/http/cmd_list_test.go delete mode 100644 server/http/cmd_zset.go delete mode 100644 server/http/cmd_zset_test.go delete mode 100644 server/http/handler.go delete mode 100644 server/http/http_io.go delete mode 100644 server/http/readme.md diff --git a/server/http/base.go b/server/http/base.go deleted file mode 100644 index 2d81907..0000000 --- a/server/http/base.go +++ /dev/null @@ -1,34 +0,0 @@ -package http - -import ( - "errors" - "fmt" - "github.com/siddontang/ledisdb/ledis" - "strings" -) - -const ( - ERR_ARGUMENT_FORMAT = "wrong number of arguments for '%s' command" - MSG_OK = "OK" -) - -var ( - ErrValue = errors.New("value is not an integer or out of range") - ErrSyntax = errors.New("syntax error") -) - -type commandFunc func(*ledis.DB, ...string) (interface{}, error) - -var regCmds = map[string]commandFunc{} - -func register(name string, f commandFunc) { - if _, ok := regCmds[strings.ToLower(name)]; ok { - panic(fmt.Sprintf("%s has been registered", name)) - } - regCmds[name] = f -} - -func lookup(name string) commandFunc { - return regCmds[strings.ToLower(name)] - -} diff --git a/server/http/base_test.go b/server/http/base_test.go deleted file mode 100644 index fee37f2..0000000 --- a/server/http/base_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package http - -import ( - "github.com/siddontang/ledisdb/ledis" - "os" - "sync" -) - -var once sync.Once -var ldb *ledis.Ledis - -func getTestDB() *ledis.DB { - f := func() { - var err error - if _, err = os.Stat("/tmp/test_http_api_db"); err == nil { - if err = os.RemoveAll("/tmp/test_http_api_db"); err != nil { - panic(err) - } - } else if !os.IsNotExist(err) { - panic(err) - } - var cfg ledis.Config - cfg.DataDir = "/tmp/test_http_api_db" - cfg.DataDB.BlockSize = 32768 - cfg.DataDB.WriteBufferSize = 20971520 - cfg.DataDB.CacheSize = 20971520 - cfg.DataDB.Compression = true - - ldb, err = ledis.Open(&cfg) - if err != nil { - panic(err) - } - } - once.Do(f) - db, err := ldb.Select(0) - if err != nil { - panic(err) - } - return db -} diff --git a/server/http/cmd_bit.go b/server/http/cmd_bit.go deleted file mode 100644 index 3efb76e..0000000 --- a/server/http/cmd_bit.go +++ /dev/null @@ -1,228 +0,0 @@ -package http - -import ( - "fmt" - "github.com/siddontang/ledisdb/ledis" - "strconv" - "strings" -) - -func bgetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bget") - } - if v, err := db.BGet([]byte(args[0])); err != nil { - return nil, err - } else { - return ledis.String(v), nil - } -} - -func bdeleteCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bdelete") - } - if n, err := db.BDelete([]byte(args[0])); err != nil { - return nil, err - } else { - return n, err - } -} - -func bsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bsetbit") - } - key := []byte(args[0]) - offset, err := strconv.ParseInt(args[1], 10, 32) - if err != nil { - return nil, ErrValue - } - val, err := strconv.ParseUint(args[2], 10, 8) - if ori, err := db.BSetBit(key, int32(offset), uint8(val)); err != nil { - return nil, err - - } else { - return ori, nil - } -} - -func bgetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bgetbit") - } - key := []byte(args[0]) - offset, err := strconv.ParseInt(args[1], 10, 32) - if err != nil { - return nil, ErrValue - } - - if v, err := db.BGetBit(key, int32(offset)); err != nil { - return nil, err - } else { - return v, nil - } - - return nil, nil -} - -func bmsetbitCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") - } - key := []byte(args[0]) - if len(args[1:])%2 != 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bmsetbit") - } else { - args = args[1:] - } - pairs := make([]ledis.BitPair, len(args)/2) - for i := 0; i < len(pairs); i++ { - offset, err := strconv.ParseInt(args[i*2], 10, 32) - if err != nil { - return nil, err - } - val, err := strconv.ParseUint(args[i*2+1], 10, 8) - if err != nil { - return nil, err - } - pairs[i].Pos = int32(offset) - pairs[i].Val = uint8(val) - } - if place, err := db.BMSetBit(key, pairs...); err != nil { - return nil, err - } else { - return place, nil - } -} - -func bcountCommand(db *ledis.DB, args ...string) (interface{}, error) { - argCnt := len(args) - if argCnt > 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bcount") - } - - var err error - var start, end int64 = 0, -1 - if argCnt > 1 { - if start, err = strconv.ParseInt(args[1], 10, 32); err != nil { - return nil, ErrValue - } - } - if argCnt > 2 { - if end, err = strconv.ParseInt(args[2], 10, 32); err != nil { - return nil, ErrValue - } - } - key := []byte(args[0]) - if cnt, err := db.BCount(key, int32(start), int32(end)); err != nil { - return nil, err - } else { - return cnt, nil - } -} - -func boptCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bopt") - } - opDesc := strings.ToLower(args[0]) - dstKey := []byte(args[1]) - - var srcKeys = [][]byte{} - if len(args) >= 3 { - srcKeys = make([][]byte, len(args[2:])) - for i, arg := range args[2:] { - srcKeys[i] = []byte(arg) - } - } - - var op uint8 - switch opDesc { - case "and": - op = ledis.OPand - case "or": - op = ledis.OPor - case "xor": - op = ledis.OPxor - case "not": - op = ledis.OPnot - default: - return nil, fmt.Errorf("invalid argument '%s' for 'bopt' command", opDesc) - } - if blen, err := db.BOperation(op, dstKey, srcKeys...); err != nil { - return nil, err - } else { - return blen, nil - } -} - -func bexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bexpire") - } - duration, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, err - } - key := []byte(args[0]) - if v, err := db.BExpire(key, duration); err != nil { - return nil, err - } else { - return v, err - } -} - -func bexpireatCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bexpireat") - } - when, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, err - } - key := []byte(args[0]) - if v, err := db.BExpireAt(key, when); err != nil { - return nil, err - } else { - return v, nil - } -} - -func bttlCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bttl") - } - key := []byte(args[0]) - if v, err := db.BTTL(key); err != nil { - return nil, err - } else { - return v, err - } -} - -func bpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "bpersist") - } - key := []byte(args[0]) - if n, err := db.BPersist(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func init() { - register("bget", bgetCommand) - register("bdelete", bdeleteCommand) - register("bsetbit", bsetbitCommand) - register("bgetbit", bgetbitCommand) - register("bmsetbit", bmsetbitCommand) - register("bcount", bcountCommand) - register("bopt", boptCommand) - register("bexpire", bexpireCommand) - register("bexpireat", bexpireatCommand) - register("bttl", bttlCommand) - register("bpersist", bpersistCommand) -} diff --git a/server/http/cmd_bit_test.go b/server/http/cmd_bit_test.go deleted file mode 100644 index f5af288..0000000 --- a/server/http/cmd_bit_test.go +++ /dev/null @@ -1,274 +0,0 @@ -package http - -import ( - "fmt" - "testing" - "time" -) - -func TestBgetCommand(t *testing.T) { - db := getTestDB() - db.BSetBit([]byte("test_bget"), 0, 1) - db.BSetBit([]byte("test_bget"), 1, 1) - db.BSetBit([]byte("test_bget"), 2, 1) - - _, err := bgetCommand(db, "test_bget", "a", "b", "c") - if err == nil || err.Error() != "wrong number of arguments for 'bget' command" { - t.Fatalf("invalid err %v", err) - } - - r, err := bgetCommand(db, "test_bget") - if err != nil { - t.Fatal(err.Error()) - } - str := r.(string) - if str != "\x07" { - t.Fatalf("wrong result of 'bget': %v", []byte(str)) - } -} - -func TestBDeleteCommand(t *testing.T) { - db := getTestDB() - - _, err := bdeleteCommand(db, "test_bdelete", "a", "b", "c") - if err == nil || err.Error() != "wrong number of arguments for 'bdelete' command" { - t.Fatalf("invalid err %v", err) - } - - db.BSetBit([]byte("test_bdelete"), 0, 1) - db.BSetBit([]byte("test_bdelete"), 1, 1) - db.BSetBit([]byte("test_bdelete"), 2, 1) - n, err := bdeleteCommand(db, "test_bdelete") - if err != nil { - t.Fatal(err.Error()) - } - if n.(int64) != 1 { - t.Fatalf("wrong result: %v", n) - } - - n, err = bdeleteCommand(db, "test_bdelete_not_exit") - if err != nil { - t.Fatal(err.Error()) - } - if n.(int64) != 0 { - t.Fatalf("wrong result: %v", n) - } -} - -func TestBSetbitCommand(t *testing.T) { - db := getTestDB() - _, err := bsetbitCommand(db, "test_bsetbit", "a", "b", "c") - if err == nil || err.Error() != "wrong number of arguments for 'bsetbit' command" { - t.Fatalf("invalid err %v", err) - } - n, err := bsetbitCommand(db, "test_bsetbit", "1", "1") - if err != nil { - t.Fatal(err.Error()) - } - if n.(uint8) != 0 { - t.Fatal("wrong result: %v", n) - } - n, err = db.BGetBit([]byte("test_bsetbit"), 1) - if err != nil { - t.Fatal(err.Error()) - } - if n.(uint8) != 1 { - t.Fatalf("wrong result: %v", n) - } -} - -func TestBMsetbitCommand(t *testing.T) { - db := getTestDB() - _, err := bmsetbitCommand(db, "test_bmsetbit", "a", "b", "c") - - if err == nil || err.Error() != "wrong number of arguments for 'bmsetbit' command" { - t.Fatalf("invalid err %v", err) - } - n, err := bmsetbitCommand(db, "test_bmsetbit", "1", "1", "3", "1") - if err != nil { - t.Fatal(err.Error()) - } - if n.(int64) != 2 { - t.Fatalf("wrong result: %v", n) - } -} - -func TestBCountCommand(t *testing.T) { - db := getTestDB() - _, err := bcountCommand(db, "test_bcount", "a", "b", "c") - if err == nil || err.Error() != "wrong number of arguments for 'bcount' command" { - t.Fatalf("invalid err %v", err) - } - - db.BSetBit([]byte("test_bcount"), 1, 1) - db.BSetBit([]byte("test_bcount"), 3, 1) - - cnt, err := bcountCommand(db, "test_bcount", "0", "3") - if err != nil { - t.Fatal(err.Error()) - } - if cnt.(int32) != 2 { - t.Fatal("invalid value", cnt) - } - - cnt, err = bcountCommand(db, "test_bcount", "2") - - if err != nil { - t.Fatal(err.Error()) - } - if cnt.(int32) != 1 { - t.Fatal("invalid value", cnt) - } - - cnt, err = bcountCommand(db, "test_bcount") - - if err != nil { - t.Fatal(err.Error()) - } - if cnt.(int32) != 2 { - t.Fatal("invalid value", cnt) - } -} - -func TestBOptCommand(t *testing.T) { - db := getTestDB() - _, err := boptCommand(db, "test_bopt") - if err == nil || err.Error() != "wrong number of arguments for 'bopt' command" { - t.Fatalf("invalid err %v", err) - } - - db.BSetBit([]byte("test_bopt_and_1"), 1, 1) - db.BSetBit([]byte("test_bopt_and_2"), 1, 1) - - _, err = boptCommand(db, "and", "test_bopt_and_3", "test_bopt_and_1", "test_bopt_and_2") - if err != nil { - t.Fatal(err.Error()) - } - - r, _ := db.BGet([]byte("test_bopt_and_3")) - if len(r) != 1 || r[0] != 2 { - t.Fatalf("invalid result %v", r) - } - - db.BSetBit([]byte("test_bopt_or_1"), 0, 1) - db.BSetBit([]byte("test_bopt_or_1"), 1, 1) - db.BSetBit([]byte("test_bopt_or_2"), 0, 1) - db.BSetBit([]byte("test_bopt_or_2"), 2, 1) - - _, err = boptCommand(db, "or", "test_bopt_or_3", "test_bopt_or_1", "test_bopt_or_2") - if err != nil { - t.Fatal(err.Error()) - } - - r, _ = db.BGet([]byte("test_bopt_or_3")) - if len(r) != 1 || r[0] != 7 { - t.Fatalf("invalid result %v", r) - } - - db.BSetBit([]byte("test_bopt_xor_1"), 0, 1) - db.BSetBit([]byte("test_bopt_xor_1"), 1, 1) - db.BSetBit([]byte("test_bopt_xor_2"), 0, 1) - db.BSetBit([]byte("test_bopt_xor_2"), 2, 1) - - _, err = boptCommand(db, "xor", "test_bopt_xor_3", "test_bopt_xor_1", "test_bopt_xor_2") - if err != nil { - t.Fatal(err.Error()) - } - - r, _ = db.BGet([]byte("test_bopt_xor_3")) - if len(r) != 1 || r[0] != 6 { - t.Fatalf("invalid result %v", r) - } - - db.BSetBit([]byte("test_bopt_not_1"), 0, 1) - db.BSetBit([]byte("test_bopt_not_1"), 1, 0) - _, err = boptCommand(db, "not", "test_bopt_not_2", "test_bopt_not_1") - if err != nil { - t.Fatal(err.Error()) - } - - r, _ = db.BGet([]byte("test_bopt_not_2")) - if len(r) != 1 || r[0] != 2 { - t.Fatalf("invalid result %v", r) - } - - _, err = boptCommand(db, "invalid_opt", "abc") - if err == nil || err.Error() != "invalid argument 'invalid_opt' for 'bopt' command" { - t.Fatal("invalid err ", err.Error()) - } -} - -func TestBExpireCommand(t *testing.T) { - db := getTestDB() - _, err := bexpireCommand(db, "test_bexpire", "a", "b") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bexpire") { - t.Fatalf("invalid err %v", err) - } - - db.BSetBit([]byte("test_bexpire"), 1, 1) - bexpireCommand(db, "test_bexpire", "1000") - - n, err := db.BTTL([]byte("test_bexpire")) - if err != nil { - t.Fatal(err.Error()) - } - if n == -1 { - t.Fatal("wrong result ", n) - } -} - -func TestBExpireAtCommand(t *testing.T) { - db := getTestDB() - _, err := bexpireatCommand(db, "test_bexpireat", "a", "b") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bexpireat") { - t.Fatalf("invalid err %v", err) - } - - db.BSetBit([]byte("test_bexpireat"), 1, 1) - expireAt := fmt.Sprintf("%d", time.Now().Unix()+100) - if _, err = bexpireatCommand(db, "test_bexpireat", expireAt); err != nil { - t.Fatal(err.Error()) - } - - n, err := db.BTTL([]byte("test_bexpireat")) - if err != nil { - t.Fatal(err.Error()) - } - if n == -1 { - t.Fatal("wrong result ", n) - } -} - -func TestBTTLCommand(t *testing.T) { - db := getTestDB() - - _, err := bttlCommand(db, "test_bttl", "a", "b") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bttl") { - t.Fatalf("invalid err %v", err) - } - - v, err := bttlCommand(db, "test_bttl") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestBPersistCommand(t *testing.T) { - - db := getTestDB() - _, err := bpersistCommand(db, "test_bpersist", "a", "b") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "bpersist") { - t.Fatalf("invalid err %v", err) - } - - v, err := bpersistCommand(db, "test_bpersist") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} diff --git a/server/http/cmd_hash.go b/server/http/cmd_hash.go deleted file mode 100644 index 23fa3b2..0000000 --- a/server/http/cmd_hash.go +++ /dev/null @@ -1,311 +0,0 @@ -package http - -import ( - "fmt" - "github.com/siddontang/ledisdb/ledis" - "strconv" -) - -func hsetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hset") - } - - key := []byte(args[0]) - field := []byte(args[1]) - value := []byte(args[2]) - if n, err := db.HSet(key, field, value); err != nil { - return nil, err - } else { - return n, err - } -} - -func hgetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hget") - } - - key := []byte(args[0]) - field := []byte(args[1]) - - if v, err := db.HGet(key, field); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func hexistsCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexists") - } - key := []byte(args[0]) - field := []byte(args[1]) - - var n int64 = 1 - if v, err := db.HGet(key, field); err != nil { - return nil, err - } else { - if v == nil { - n = 0 - } - return n, nil - } -} - -func hdelCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hdel") - } - key := []byte(args[0]) - fields := make([][]byte, len(args[1:])) - for i, arg := range args[1:] { - fields[i] = []byte(arg) - } - - if n, err := db.HDel(key, fields...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func hlenCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hlen") - } - key := []byte(args[0]) - if n, err := db.HLen(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func hincrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hincrby") - } - key := []byte(args[0]) - field := []byte(args[1]) - delta, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return nil, ErrValue - } - - var n int64 - if n, err = db.HIncrBy(key, field, delta); err != nil { - return nil, err - } else { - return n, nil - } -} - -func hmsetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmset") - } - - if len(args[1:])%2 != 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmset") - } - key := []byte(args[0]) - args = args[1:] - kvs := make([]ledis.FVPair, len(args)/2) - for i := 0; i < len(kvs); i++ { - kvs[i].Field = []byte(args[2*i]) - kvs[i].Value = []byte(args[2*i+1]) - } - if err := db.HMset(key, kvs...); err != nil { - return nil, err - } else { - return []interface{}{true, MSG_OK}, nil - } -} - -func hmgetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmget") - } - key := []byte(args[0]) - fields := make([][]byte, len(args[1:])) - for i, arg := range args[1:] { - fields[i] = []byte(arg) - } - if vals, err := db.HMget(key, fields...); err != nil { - return nil, err - } else { - arr := make([]interface{}, len(vals)) - for i, v := range vals { - if v == nil { - arr[i] = nil - } else { - arr[i] = ledis.String(v) - } - } - return arr, nil - } -} - -func hgetallCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hgetall") - } - key := []byte(args[0]) - if fvs, err := db.HGetAll(key); err != nil { - return nil, err - } else { - var m = make(map[string]string) - for _, fv := range fvs { - m[ledis.String(fv.Field)] = ledis.String(fv.Value) - } - return m, nil - } -} - -func hkeysCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hkeys") - } - key := []byte(args[0]) - if fields, err := db.HKeys(key); err != nil { - return nil, err - } else { - arr := make([]string, len(fields)) - for i, f := range fields { - arr[i] = ledis.String(f) - } - return arr, nil - } -} - -func hvalsCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hvals") - } - key := []byte(args[0]) - if vals, err := db.HValues(key); err != nil { - return nil, err - } else { - var arr = make([]string, len(vals)) - for i, v := range vals { - arr[i] = ledis.String(v) - } - return arr, nil - } -} - -func hclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hclear") - } - key := []byte(args[0]) - if n, err := db.HClear(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func hmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hmclear") - } - keys := make([][]byte, len(args)) - for i, arg := range args { - keys[i] = []byte(arg) - } - - if n, err := db.HMclear(keys...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func hexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexpire") - } - key := []byte(args[0]) - duration, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - if v, err := db.HExpire(key, duration); err != nil { - return nil, err - } else { - return v, nil - } -} - -func hexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hexpireat") - } - key := []byte(args[0]) - - when, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - if v, err := db.HExpireAt(key, when); err != nil { - return nil, err - } else { - return v, nil - } -} - -func httlCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "httl") - } - - key := []byte(args[0]) - if v, err := db.HTTL(key); err != nil { - return nil, err - } else { - return v, nil - } -} - -func hpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "hpersist") - } - key := []byte(args[0]) - if n, err := db.HPersist(key); err != nil { - return nil, err - } else { - return n, err - } -} - -func init() { - register("hdel", hdelCommand) - register("hexists", hexistsCommand) - register("hget", hgetCommand) - register("hgetall", hgetallCommand) - register("hincrby", hincrbyCommand) - register("hkeys", hkeysCommand) - register("hlen", hlenCommand) - register("hmget", hmgetCommand) - register("hmset", hmsetCommand) - register("hset", hsetCommand) - register("hvals", hvalsCommand) - - //ledisdb special command - - register("hclear", hclearCommand) - register("hmclear", hmclearCommand) - register("hexpire", hexpireCommand) - register("hexpireat", hexpireAtCommand) - register("httl", httlCommand) - register("hpersist", hpersistCommand) - -} diff --git a/server/http/cmd_hash_test.go b/server/http/cmd_hash_test.go deleted file mode 100644 index bbca2ed..0000000 --- a/server/http/cmd_hash_test.go +++ /dev/null @@ -1,369 +0,0 @@ -package http - -import ( - "fmt" - "testing" - "time" -) - -func TestHSetCommand(t *testing.T) { - db := getTestDB() - _, err := hsetCommand(db, "test_hset") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hset") { - t.Fatalf("invalid err %v", err) - } - - n, err := hsetCommand(db, "test_hset", "f", "v") - if err != nil { - t.Fatal(err) - } - if n.(int64) != 1 { - t.Fatal("invalid result ", n) - } - v, err := db.HGet([]byte("test_hset"), []byte("f")) - if err != nil { - t.Fatal(err.Error()) - } - if string(v) != "v" { - t.Fatalf("invalid result %s", v) - } - -} - -func TestHGetCommand(t *testing.T) { - db := getTestDB() - _, err := hgetCommand(db, "test_hget") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hget") { - t.Fatalf("invalid err %v", err) - } - if _, err := db.HSet([]byte("test_hget"), []byte("f"), []byte("v")); err != nil { - t.Fatal(err.Error()) - } - - v, err := hgetCommand(db, "test_hget", "f") - if err != nil { - t.Fatal(err.Error()) - } - if v.(string) != "v" { - t.Fatal("invalid result ", v) - } - -} - -func TestHExistsCommand(t *testing.T) { - db := getTestDB() - _, err := hexistsCommand(db, "test_hexists") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexists") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hexists"), []byte("f"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - - v, err := hexistsCommand(db, "test_hexists", "f") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } - -} - -func TestHDelCommand(t *testing.T) { - db := getTestDB() - _, err := hdelCommand(db, "test_hdel") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hdel") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hdel"), []byte("f"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - - v, err := hdelCommand(db, "test_hdel", "f") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } - - r, err := db.HGet([]byte("test_hdel"), []byte("f")) - if err != nil { - t.Fatal(err.Error()) - } - if r != nil { - t.Fatalf("invalid result %v", r) - } -} - -func TestHLenCommand(t *testing.T) { - db := getTestDB() - _, err := hlenCommand(db, "test_hlen", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hlen") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hlen"), []byte("f1"), []byte("v1")) - if err != nil { - t.Fatal(err.Error()) - } - _, err = db.HSet([]byte("test_hlen"), []byte("f2"), []byte("v2")) - - if err != nil { - t.Fatal(err.Error()) - } - - v, err := hlenCommand(db, "test_hlen") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 2 { - t.Fatal("invalid result ", v) - } -} - -func TestHIncrbyCommand(t *testing.T) { - db := getTestDB() - _, err := hincrbyCommand(db, "test_hincrby") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hincrby") { - t.Fatalf("invalid err %v", err) - } - _, err = db.HSet([]byte("test_hincrby"), []byte("f"), []byte("10")) - if err != nil { - t.Fatal(err.Error()) - } - - _, err = hincrbyCommand(db, "test_hincrby", "f", "x") - if err != ErrValue { - t.Fatal("invalid err ", err) - } - - v, err := hincrbyCommand(db, "test_hincrby", "f", "10") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 20 { - t.Fatal("invalid result ", v) - } -} - -func TestHMsetCommand(t *testing.T) { - db := getTestDB() - _, err := hmsetCommand(db, "test_hmset") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmset") { - t.Fatalf("invalid err %v", err) - } - - _, err = hmsetCommand(db, "test_hmset", "f1", "v1", "f2", "v2") - if err != nil { - t.Fatal(err.Error()) - } - - v, err := db.HGet([]byte("test_hmset"), []byte("f1")) - - if err != nil { - t.Fatal(err.Error()) - } - if string(v) != "v1" { - t.Fatalf("invalid result %s", v) - } - - v, err = db.HGet([]byte("test_hmset"), []byte("f2")) - - if err != nil { - t.Fatal(err.Error()) - } - if string(v) != "v2" { - t.Fatalf("invalid result %s", v) - } -} - -func TestHMgetCommand(t *testing.T) { - db := getTestDB() - _, err := hmgetCommand(db, "test_hmget") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmget") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hmget"), []byte("f1"), []byte("v1")) - if err != nil { - t.Fatal(err.Error()) - } - - _, err = db.HSet([]byte("test_hmget"), []byte("f2"), []byte("v2")) - if err != nil { - t.Fatal(err.Error()) - } - - v, err := hmgetCommand(db, "test_hmget", "f1", "f2") - - if err != nil { - t.Fatal(err.Error()) - } - arr := v.([]interface{}) - if len(arr) != 2 { - t.Fatalf("invalid arr %v", arr) - } - if arr[0].(string) != "v1" { - t.Fatal("invalid result ", v) - } - - if arr[1].(string) != "v2" { - t.Fatal("invalid result ", v) - } -} - -func TestHGetallCommand(t *testing.T) { - db := getTestDB() - _, err := hgetallCommand(db, "test_hgetall", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hgetall") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hgetall"), []byte("f"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - v, err := hgetallCommand(db, "test_hgetall") - if err != nil { - t.Fatal(err.Error()) - } - m := v.(map[string]string) - if m["f"] != "v" { - t.Fatal("invalid result ", v) - } -} - -func TestHKeysCommand(t *testing.T) { - db := getTestDB() - _, err := hkeysCommand(db, "test_hkeys", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hkeys") { - t.Fatalf("invalid err %v", err) - } - _, err = db.HSet([]byte("test_hkeys"), []byte("f"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - v, err := hkeysCommand(db, "test_hkeys") - if err != nil { - t.Fatal(err.Error()) - } - arr := v.([]string) - if arr[0] != "f" { - t.Fatal("invalid result ", v) - } - -} - -func TestHClearCommand(t *testing.T) { - db := getTestDB() - _, err := hclearCommand(db, "test_hclear", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hclear") { - t.Fatalf("invalid err %v", err) - } - - _, err = db.HSet([]byte("test_hclear"), []byte("f"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - - v, err := hclearCommand(db, "test_hclear") - - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } -} - -func TestHMclearCommand(t *testing.T) { - db := getTestDB() - _, err := hmclearCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hmclear") { - t.Fatalf("invalid err %v", err) - } - - v, err := hmclearCommand(db, "test_hmclear1", "test_hmclear2") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 2 { - t.Fatal("invalid result ", v) - } -} - -func TestHExpireCommand(t *testing.T) { - db := getTestDB() - _, err := hexpireCommand(db, "test_hexpire") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexpire") { - t.Fatalf("invalid err %v", err) - } - v, err := hexpireCommand(db, "test_hexpire", "10") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestHExpireAtCommand(t *testing.T) { - db := getTestDB() - _, err := hexpireAtCommand(db, "test_hexpireat") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hexpireat") { - t.Fatalf("invalid err %v", err) - } - - expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) - v, err := hexpireCommand(db, "test_hexpireat", expireAt) - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestHTTLCommand(t *testing.T) { - db := getTestDB() - _, err := httlCommand(db, "test_httl", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "httl") { - t.Fatalf("invalid err %v", err) - } - - v, err := httlCommand(db, "test_httl") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestHPersistCommand(t *testing.T) { - db := getTestDB() - _, err := hpersistCommand(db, "test_hpersist", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "hpersist") { - t.Fatalf("invalid err %v", err) - } - - v, err := hpersistCommand(db, "test_hpersist") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} diff --git a/server/http/cmd_kv.go b/server/http/cmd_kv.go deleted file mode 100644 index dbd1116..0000000 --- a/server/http/cmd_kv.go +++ /dev/null @@ -1,278 +0,0 @@ -package http - -import ( - "fmt" - "github.com/siddontang/ledisdb/ledis" - "strconv" -) - -func getCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "get") - } - key := []byte(args[0]) - if v, err := db.Get(key); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func setCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "set") - } - - key := []byte(args[0]) - val := []byte(args[1]) - if err := db.Set(key, val); err != nil { - return nil, err - } else { - return []interface{}{true, MSG_OK}, nil - } - -} - -func getsetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "getset") - } - - key := []byte(args[0]) - val := []byte(args[1]) - if v, err := db.GetSet(key, val); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func setnxCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "setnx") - } - - key := []byte(args[0]) - val := []byte(args[1]) - if n, err := db.SetNX(key, val); err != nil { - return nil, err - } else { - return n, nil - } -} - -func existsCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "exists") - } - - if n, err := db.Exists([]byte(args[0])); err != nil { - return nil, err - } else { - return n, nil - } -} - -func incrCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "incr") - } - - key := []byte(args[0]) - if n, err := db.Incr(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func decrCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "decr") - } - - key := []byte(args[0]) - if n, err := db.Decr(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func incrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "incrby") - } - - delta, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - - if n, err := db.IncryBy(key, delta); err != nil { - return nil, err - } else { - return n, nil - } -} - -func decrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "decrby") - } - - delta, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - - if n, err := db.DecrBy(key, delta); err != nil { - return nil, err - } else { - return n, nil - } -} - -func delCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) == 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "del") - } - - keys := make([][]byte, len(args)) - if n, err := db.Del(keys...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func msetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) == 0 || len(args)%2 != 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "mset") - } - - kvs := make([]ledis.KVPair, len(args)/2) - for i := 0; i < len(kvs); i++ { - kvs[i].Key = []byte(args[2*i]) - kvs[i].Value = []byte(args[2*i+1]) - } - - if err := db.MSet(kvs...); err != nil { - return nil, err - } else { - return []interface{}{true, MSG_OK}, nil - } -} - -func mgetCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) == 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "mget") - } - - keys := make([][]byte, len(args)) - for i, arg := range args { - keys[i] = []byte(arg) - } - if vals, err := db.MGet(keys...); err != nil { - return nil, err - } else { - arr := make([]interface{}, len(vals)) - for i, v := range vals { - if v == nil { - arr[i] = nil - } else { - arr[i] = ledis.String(v) - } - } - return arr, nil - } -} - -func expireCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "expire") - } - - duration, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - key := []byte(args[0]) - if v, err := db.Expire(key, duration); err != nil { - return nil, err - } else { - return v, nil - } -} - -func expireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "expireat") - } - - when, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - key := []byte(args[0]) - if v, err := db.ExpireAt(key, when); err != nil { - return nil, err - } else { - return v, nil - } -} - -func ttlCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "ttl") - } - key := []byte(args[0]) - - if v, err := db.TTL(key); err != nil { - return nil, err - } else { - return v, nil - } -} - -func persistCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "persist") - } - key := []byte(args[0]) - - if n, err := db.Persist(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func init() { - register("decr", decrCommand) - register("decrby", decrbyCommand) - register("del", delCommand) - register("exists", existsCommand) - register("get", getCommand) - register("getset", getsetCommand) - register("incr", incrCommand) - register("incrby", incrbyCommand) - register("mget", mgetCommand) - register("mset", msetCommand) - register("set", setCommand) - register("setnx", setnxCommand) - register("expire", expireCommand) - register("expireat", expireAtCommand) - register("ttl", ttlCommand) - register("persist", persistCommand) -} diff --git a/server/http/cmd_kv_test.go b/server/http/cmd_kv_test.go deleted file mode 100644 index b85de30..0000000 --- a/server/http/cmd_kv_test.go +++ /dev/null @@ -1,261 +0,0 @@ -package http - -import ( - "fmt" - "testing" - "time" -) - -func TestGetCommand(t *testing.T) { - db := getTestDB() - _, err := getCommand(db, "test_get", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "get") { - t.Fatal("invalid err ", err) - } - - err = db.Set([]byte("test_get"), []byte("v")) - if err != nil { - t.Fatal(err.Error()) - } - v, err := getCommand(db, "test_get") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(string) != "v" { - t.Fatalf("invalid result %v", v) - } - -} - -func TestSetCommand(t *testing.T) { - db := getTestDB() - _, err := setCommand(db, "test_set") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "set") { - t.Fatal("invalid err ", err) - } - v, err := setCommand(db, "test_set", "v") - if err != nil { - t.Fatal(err.Error()) - } - r := v.([]interface{}) - if len(r) != 2 { - t.Fatalf("invalid result %v", v) - } - - if r[0].(bool) != true { - t.Fatalf("invalid result %v", r[0]) - } - - if r[1].(string) != "OK" { - t.Fatalf("invalid result %v", r[1]) - } -} - -func TestGetsetCommand(t *testing.T) { - db := getTestDB() - _, err := getsetCommand(db, "test_getset") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "getset") { - t.Fatal("invalid err ", err) - } - - v, err := getsetCommand(db, "test_getset", "v") - if err != nil { - t.Fatal(err.Error()) - } - if v != nil { - t.Fatal("invalid result ", v) - } -} - -func TestSetnxCommand(t *testing.T) { - db := getTestDB() - _, err := setnxCommand(db, "test_setnx") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "setnx") { - t.Fatal("invalid err ", err) - } - v, err := setnxCommand(db, "test_setnx", "v") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } -} - -func TestExistsCommand(t *testing.T) { - db := getTestDB() - _, err := existsCommand(db, "test_exists", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "exists") { - t.Fatal("invalid err ", err) - } - v, err := existsCommand(db, "test_exists") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestIncrCommand(t *testing.T) { - db := getTestDB() - _, err := incrCommand(db, "test_incr", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "incr") { - t.Fatal("invalid err ", err) - } - v, err := incrCommand(db, "test_incr") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } - -} - -func TestDecrCommand(t *testing.T) { - db := getTestDB() - _, err := decrCommand(db, "test_decr", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "decr") { - t.Fatal("invalid err ", err) - } - - v, err := decrCommand(db, "test_decr") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestDelCommand(t *testing.T) { - db := getTestDB() - _, err := delCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "del") { - t.Fatal("invalid err ", err) - } - - v, err := delCommand(db, "test_del") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } -} - -func TestMsetCommand(t *testing.T) { - db := getTestDB() - _, err := msetCommand(db, "a", "b", "c") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "mset") { - t.Fatal("invalid err ", err) - } - - v, err := msetCommand(db, "test_mset", "v") - - if err != nil { - t.Fatal(err.Error()) - } - r := v.([]interface{}) - if len(r) != 2 { - t.Fatal("invalid result ", v) - } - if r[0].(bool) != true { - t.Fatal("invalid result ", r[0]) - } - - if r[1].(string) != "OK" { - t.Fatal("invalid result ", r[1]) - } -} - -func TestMgetCommand(t *testing.T) { - db := getTestDB() - _, err := mgetCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "mget") { - t.Fatal("invalid err ", err) - } - - v, err := mgetCommand(db, "test_mget") - - if err != nil { - t.Fatal(err.Error()) - } - arr := v.([]interface{}) - if arr[0] != nil { - t.Fatal("invalid result ", arr) - } -} - -func TestExpireCommand(t *testing.T) { - db := getTestDB() - _, err := expireCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "expire") { - t.Fatal("invalid err ", err) - } - v, err := expireCommand(db, "test_expire", "10") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestExpireAtCommand(t *testing.T) { - db := getTestDB() - _, err := expireAtCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "expireat") { - t.Fatal("invalid err ", err) - } - - expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) - v, err := expireAtCommand(db, "test_expireat", expireAt) - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestTTLCommand(t *testing.T) { - db := getTestDB() - _, err := ttlCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "ttl") { - t.Fatal("invalid err ", err) - } - - v, err := ttlCommand(db, "test_ttl") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestPersistCommand(t *testing.T) { - db := getTestDB() - _, err := persistCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "persist") { - t.Fatal("invalid err ", err) - } - - v, err := persistCommand(db, "test_persist") - - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} diff --git a/server/http/cmd_list.go b/server/http/cmd_list.go deleted file mode 100644 index 3f83b8e..0000000 --- a/server/http/cmd_list.go +++ /dev/null @@ -1,248 +0,0 @@ -package http - -import ( - "fmt" - "github.com/siddontang/ledisdb/ledis" - "strconv" -) - -func lpushCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpush") - } - key := []byte(args[0]) - elems := make([][]byte, len(args[1:])) - for i, arg := range args[1:] { - elems[i] = []byte(arg) - } - - if n, err := db.LPush(key, elems...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func rpushCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "rpush") - } - - key := []byte(args[0]) - elems := make([][]byte, len(args[1:])) - for i, arg := range args[1:] { - elems[i] = []byte(arg) - } - if n, err := db.RPush(key, elems...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func lpopCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpop") - } - - key := []byte(args[0]) - - if v, err := db.LPop(key); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func rpopCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "rpop") - } - key := []byte(args[0]) - - if v, err := db.RPop(key); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func llenCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "llen") - } - - key := []byte(args[0]) - if n, err := db.LLen(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func lindexCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lindex") - } - - index, err := strconv.ParseInt(args[1], 10, 32) - if err != nil { - return nil, ErrValue - } - key := []byte(args[0]) - - if v, err := db.LIndex(key, int32(index)); err != nil { - return nil, err - } else { - if v == nil { - return nil, nil - } - return ledis.String(v), nil - } -} - -func lrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lrange") - } - - start, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - stop, err := strconv.ParseInt(args[2], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - if vals, err := db.LRange(key, int32(start), int32(stop)); err != nil { - return nil, err - } else { - arr := make([]interface{}, len(vals)) - for i, v := range vals { - if v == nil { - arr[i] = nil - } else { - arr[i] = ledis.String(v) - } - } - return arr, nil - } -} - -func lclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lclear") - } - - key := []byte(args[0]) - if n, err := db.LClear(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func lmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lmclear") - } - - keys := make([][]byte, len(args)) - for i, arg := range args { - keys[i] = []byte(arg) - } - if n, err := db.LMclear(keys...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func lexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lexpire") - } - - duration, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - if v, err := db.LExpire(key, duration); err != nil { - return nil, err - } else { - return v, nil - } -} - -func lexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lexpireat") - } - - when, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - if v, err := db.LExpireAt(key, when); err != nil { - return nil, err - } else { - return v, nil - } -} - -func lttlCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lttl") - } - - key := []byte(args[0]) - if v, err := db.LTTL(key); err != nil { - return nil, err - } else { - return v, nil - } -} - -func lpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "lpersist") - } - key := []byte(args[0]) - if n, err := db.LPersist(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func init() { - register("lindex", lindexCommand) - register("llen", llenCommand) - register("lpop", lpopCommand) - register("lrange", lrangeCommand) - register("lpush", lpushCommand) - register("rpop", rpopCommand) - register("rpush", rpushCommand) - - //ledisdb special command - - register("lclear", lclearCommand) - register("lmclear", lmclearCommand) - register("lexpire", lexpireCommand) - register("lexpireat", lexpireAtCommand) - register("lttl", lttlCommand) - register("lpersist", lpersistCommand) -} diff --git a/server/http/cmd_list_test.go b/server/http/cmd_list_test.go deleted file mode 100644 index f280f28..0000000 --- a/server/http/cmd_list_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package http - -import ( - "fmt" - "testing" - "time" -) - -func TestLpushCommand(t *testing.T) { - db := getTestDB() - _, err := lpushCommand(db, "test_lpush") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpush") { - t.Fatal("invalid err ", err) - } - - v, err := lpushCommand(db, "test_lpush", "1", "2") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 2 { - t.Fatal("invalid result", v) - } -} - -func TestRpushCommand(t *testing.T) { - db := getTestDB() - _, err := rpushCommand(db, "test_rpush") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "rpush") { - t.Fatal("invalid err ", err) - } - - v, err := rpushCommand(db, "test_rpush", "1", "2") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 2 { - t.Fatal("invalid result", v) - } -} - -func TestLpopCommand(t *testing.T) { - db := getTestDB() - _, err := lpopCommand(db, "test_lpop", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpop") { - t.Fatal("invalid err ", err) - } - - v, err := lpopCommand(db, "test_lpop") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result", v) - } -} - -func TestRpopCommand(t *testing.T) { - db := getTestDB() - _, err := rpopCommand(db, "test_rpop", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "rpop") { - t.Fatal("invalid err ", err) - } - - v, err := rpopCommand(db, "test_lpop") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result", v) - } -} - -func TestLlenCommand(t *testing.T) { - db := getTestDB() - _, err := llenCommand(db, "test_llen", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "llen") { - t.Fatal("invalid err ", err) - } - - v, err := llenCommand(db, "test_llen") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result", v) - } -} - -func TestLindexCommand(t *testing.T) { - db := getTestDB() - _, err := lindexCommand(db, "test_lindex") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lindex") { - t.Fatal("invalid err ", err) - } - v, err := lindexCommand(db, "test_lindex", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result", v) - } -} - -func TestLrangeCommand(t *testing.T) { - db := getTestDB() - _, err := lrangeCommand(db, "test_lrange") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lrange") { - t.Fatal("invalid err ", err) - } - v, err := lrangeCommand(db, "test_lrange", "1", "2") - if err != nil { - t.Fatal(err.Error()) - } - - arr := v.([]interface{}) - if len(arr) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestLclearCommand(t *testing.T) { - db := getTestDB() - _, err := lclearCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lclear") { - t.Fatal("invalid err ", err) - } - v, err := lclearCommand(db, "test_lclear") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestLmclearCommand(t *testing.T) { - db := getTestDB() - _, err := lmclearCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lmclear") { - t.Fatal("invalid err ", err) - } - v, err := lmclearCommand(db, "test_lmclear") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } -} - -func TestLexpireCommand(t *testing.T) { - db := getTestDB() - _, err := lexpireCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lexpire") { - t.Fatal("invalid err ", err) - } - v, err := lexpireCommand(db, "test_lexpire", "10") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestLexpireAtCommand(t *testing.T) { - db := getTestDB() - _, err := lexpireAtCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lexpireat") { - t.Fatal("invalid err ", err) - } - expireAt := fmt.Sprintf("%d", time.Now().Unix()) - v, err := lexpireCommand(db, "test_lexpireat", expireAt) - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestLTTLCommand(t *testing.T) { - db := getTestDB() - _, err := lttlCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lttl") { - t.Fatal("invalid err ", err) - } - v, err := lttlCommand(db, "test_lttl") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestLpersistCommand(t *testing.T) { - db := getTestDB() - _, err := lpersistCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "lpersist") { - t.Fatal("invalid err ", err) - } - - v, err := lpersistCommand(db, "test_lpersist") - if err != nil { - t.Fatal(err.Error()) - } - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} diff --git a/server/http/cmd_zset.go b/server/http/cmd_zset.go deleted file mode 100644 index 55341d5..0000000 --- a/server/http/cmd_zset.go +++ /dev/null @@ -1,527 +0,0 @@ -package http - -import ( - "errors" - "fmt" - "github.com/siddontang/ledisdb/ledis" - "math" - "strconv" - "strings" -) - -var errScoreOverflow = errors.New("zset score overflow") - -func zaddCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zadd") - } - - if len(args[1:])%2 != 0 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zadd") - } - - key := []byte(args[0]) - args = args[1:] - - params := make([]ledis.ScorePair, len(args)/2) - for i := 0; i < len(params); i++ { - score, err := strconv.ParseInt(args[2*i], 10, 64) - if err != nil { - return nil, ErrValue - } - - params[i].Score = score - params[i].Member = []byte(args[2*i+1]) - } - - if n, err := db.ZAdd(key, params...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zcardCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zcard") - } - - key := []byte(args[0]) - if n, err := db.ZCard(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zscore") - } - - key := []byte(args[0]) - member := []byte(args[1]) - - if s, err := db.ZScore(key, member); err != nil { - if err == ledis.ErrScoreMiss { - return nil, nil - } else { - return nil, err - } - } else { - return strconv.FormatInt(s, 10), nil - } -} - -func zremCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrem") - } - - key := []byte(args[0]) - members := make([][]byte, len(args[1:])) - for i, arg := range args[1:] { - members[i] = []byte(arg) - } - if n, err := db.ZRem(key, members...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zincrbyCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zincrby") - } - - key := []byte(args[0]) - - delta, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - member := []byte(args[2]) - if v, err := db.ZIncrBy(key, delta, member); err != nil { - return nil, err - } else { - return strconv.FormatInt(v, 10), nil - } -} - -func zparseScoreRange(minBuf string, maxBuf string) (min int64, max int64, err error) { - if strings.ToLower(minBuf) == "-inf" { - min = math.MinInt64 - } else { - var lopen bool = false - - if len(minBuf) == 0 { - err = ErrValue - return - } - - if minBuf[0] == '(' { - lopen = true - minBuf = minBuf[1:] - } - - min, err = strconv.ParseInt(minBuf, 10, 64) - if err != nil { - err = ErrValue - return - } - - if min <= ledis.MinScore || min >= ledis.MaxScore { - err = errScoreOverflow - return - } - - if lopen { - min++ - } - } - - if strings.ToLower(maxBuf) == "+inf" { - max = math.MaxInt64 - } else { - var ropen = false - - if len(maxBuf) == 0 { - err = ErrValue - return - } - - if maxBuf[0] == '(' { - ropen = true - maxBuf = maxBuf[1:] - } - - max, err = strconv.ParseInt(maxBuf, 10, 64) - if err != nil { - err = ErrValue - return - } - - if max <= ledis.MinScore || max >= ledis.MaxScore { - err = errScoreOverflow - return - } - - if ropen { - max-- - } - } - return -} - -func zcountCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zcount") - } - - min, max, err := zparseScoreRange(args[1], args[2]) - if err != nil { - return nil, err - } - - if min > max { - return 0, nil - } - - key := []byte(args[0]) - if n, err := db.ZCount(key, min, max); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zrankCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrank") - } - key := []byte(args[0]) - member := []byte(args[1]) - - if n, err := db.ZRank(key, member); err != nil { - return nil, err - } else if n == -1 { - return nil, nil - } else { - return n, nil - } -} - -func zrevrankCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrank") - } - - key := []byte(args[0]) - member := []byte(args[1]) - if n, err := db.ZRevRank(key, member); err != nil { - return nil, err - } else if n == -1 { - return nil, nil - } else { - return n, nil - } -} - -func zremrangebyrankCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zremrangebyrank") - } - - key := []byte(args[0]) - - start, err := strconv.Atoi(args[1]) - if err != nil { - return nil, ErrValue - } - stop, err := strconv.Atoi(args[2]) - - if err != nil { - return nil, ErrValue - } - - if n, err := db.ZRemRangeByRank(key, start, stop); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zremrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zremrangebyscore") - } - - key := []byte(args[0]) - min, max, err := zparseScoreRange(args[1], args[2]) - if err != nil { - return nil, err - } - - if n, err := db.ZRemRangeByScore(key, min, max); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zrangeGeneric(db *ledis.DB, reverse bool, args ...string) (interface{}, error) { - - key := []byte(args[0]) - - start, err := strconv.Atoi(args[1]) - if err != nil { - return nil, ErrValue - } - - stop, err := strconv.Atoi(args[2]) - if err != nil { - return nil, ErrValue - } - - args = args[3:] - var withScores bool = false - - if len(args) > 0 { - if len(args) != 1 { - return nil, ErrSyntax - } - if strings.ToLower(args[0]) == "withscores" { - withScores = true - } else { - return nil, ErrSyntax - } - } - - if datas, err := db.ZRangeGeneric(key, start, stop, reverse); err != nil { - return nil, err - } else { - return makeScorePairArray(datas, withScores), nil - } -} - -func makeScorePairArray(datas []ledis.ScorePair, withScores bool) []string { - var arr []string - if withScores { - arr = make([]string, 2*len(datas)) - for i, data := range datas { - arr[2*i] = ledis.String(data.Member) - arr[2*i+1] = strconv.FormatInt(data.Score, 10) - } - } else { - arr = make([]string, len(datas)) - for i, data := range datas { - arr[i] = ledis.String(data.Member) - } - } - return arr -} - -func zrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrange") - } - return zrangeGeneric(db, false, args...) -} - -func zrevrangeCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrange") - } - return zrangeGeneric(db, true, args...) -} - -func zrangebyscoreGeneric(db *ledis.DB, reverse bool, args ...string) (interface{}, error) { - key := []byte(args[0]) - - var minScore, maxScore string - - if !reverse { - minScore, maxScore = args[1], args[2] - } else { - minScore, maxScore = args[2], args[1] - } - - min, max, err := zparseScoreRange(minScore, maxScore) - - if err != nil { - return nil, err - } - - args = args[3:] - - var withScores bool = false - - if len(args) > 0 && strings.ToLower(args[0]) == "withscores" { - withScores = true - args = args[1:] - } - - var offset int = 0 - var count int = -1 - - if len(args) > 0 { - if len(args) != 3 { - return nil, ErrSyntax - } - - if strings.ToLower(args[0]) != "limit" { - return nil, ErrSyntax - } - - if offset, err = strconv.Atoi(args[1]); err != nil { - return nil, ErrValue - } - - if count, err = strconv.Atoi(args[2]); err != nil { - return nil, ErrValue - } - } - - if offset < 0 { - return []interface{}{}, nil - } - - if datas, err := db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { - return nil, err - } else { - return makeScorePairArray(datas, withScores), nil - } -} - -func zrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrangebyscore") - } - return zrangebyscoreGeneric(db, false, args...) -} - -func zrevrangebyscoreCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 3 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zrevrangebyscore") - } - return zrangebyscoreGeneric(db, true, args...) -} - -func zclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zclear") - } - - key := []byte(args[0]) - if n, err := db.ZClear(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zmclearCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) < 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zmclear") - } - - keys := make([][]byte, len(args)) - for i, arg := range args { - keys[i] = []byte(arg) - } - if n, err := db.ZMclear(keys...); err != nil { - return nil, err - } else { - return n, nil - } -} - -func zexpireCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zexpire") - } - - duration, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - if v, err := db.ZExpire(key, duration); err != nil { - return nil, err - } else { - return v, nil - } -} - -func zexpireAtCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 2 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zexpireat") - } - - when, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return nil, ErrValue - } - - key := []byte(args[0]) - if v, err := db.ZExpireAt(key, when); err != nil { - return nil, err - } else { - return v, nil - } -} - -func zttlCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zttl") - } - - key := []byte(args[0]) - if v, err := db.ZTTL(key); err != nil { - return nil, err - } else { - return v, nil - } -} - -func zpersistCommand(db *ledis.DB, args ...string) (interface{}, error) { - if len(args) != 1 { - return nil, fmt.Errorf(ERR_ARGUMENT_FORMAT, "zpersist") - } - - key := []byte(args[0]) - if n, err := db.ZPersist(key); err != nil { - return nil, err - } else { - return n, nil - } -} - -func init() { - register("zadd", zaddCommand) - register("zcard", zcardCommand) - register("zcount", zcountCommand) - register("zincrby", zincrbyCommand) - register("zrange", zrangeCommand) - register("zrangebyscore", zrangebyscoreCommand) - register("zrank", zrankCommand) - register("zrem", zremCommand) - register("zremrangebyrank", zremrangebyrankCommand) - register("zremrangebyscore", zremrangebyscoreCommand) - register("zrevrange", zrevrangeCommand) - register("zrevrank", zrevrankCommand) - register("zrevrangebyscore", zrevrangebyscoreCommand) - register("zscore", zscoreCommand) - - //ledisdb special command - - register("zclear", zclearCommand) - register("zmclear", zmclearCommand) - register("zexpire", zexpireCommand) - register("zexpireat", zexpireAtCommand) - register("zttl", zttlCommand) - register("zpersist", zpersistCommand) -} diff --git a/server/http/cmd_zset_test.go b/server/http/cmd_zset_test.go deleted file mode 100644 index 27337d4..0000000 --- a/server/http/cmd_zset_test.go +++ /dev/null @@ -1,299 +0,0 @@ -package http - -import ( - "fmt" - "testing" - "time" -) - -func TestZAddCommand(t *testing.T) { - db := getTestDB() - _, err := zaddCommand(db, "test_zadd") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zadd") { - t.Fatal("invalid err ", err) - } - - v, err := zaddCommand(db, "test_zadd", "10", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } - -} - -func TestZCardCommand(t *testing.T) { - db := getTestDB() - _, err := zcardCommand(db, "test_zcard", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zcard") { - t.Fatal("invalid err ", err) - } - - v, err := zcardCommand(db, "test_zcard") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZScore(t *testing.T) { - db := getTestDB() - _, err := zscoreCommand(db, "test_zscore") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zscore") { - t.Fatal("invalid err ", err) - } - v, err := zscoreCommand(db, "test_zscore", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result ", v) - } -} - -func TestZRemCommand(t *testing.T) { - db := getTestDB() - _, err := zremCommand(db, "test_zrem") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrem") { - t.Fatal("invalid err ", err) - } - v, err := zremCommand(db, "test_zrem", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZIncrbyCommand(t *testing.T) { - db := getTestDB() - _, err := zincrbyCommand(db, "test_zincrby") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zincrby") { - t.Fatal("invalid err ", err) - } - v, err := zincrbyCommand(db, "test_zincrby", "10", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(string) != "10" { - t.Fatal("invalid result ", v) - } -} - -func TestZCountCommand(t *testing.T) { - db := getTestDB() - _, err := zcountCommand(db, "test_zcount") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zcount") { - t.Fatal("invalid err ", err) - } - v, err := zcountCommand(db, "test_zcount", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZRankCommand(t *testing.T) { - db := getTestDB() - _, err := zrankCommand(db, "test_zrank") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrank") { - t.Fatal("invalid err ", err) - } - v, err := zrankCommand(db, "test_zcount", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result ", v) - } -} - -func TestZRevrankCommand(t *testing.T) { - db := getTestDB() - _, err := zrevrankCommand(db, "test_zrevrank") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrevrank") { - t.Fatal("invalid err ", err) - } - v, err := zrevrankCommand(db, "test_zrevrank", "m") - if err != nil { - t.Fatal(err.Error()) - } - - if v != nil { - t.Fatal("invalid result ", v) - } -} - -func TestZRemrangebyrankCommand(t *testing.T) { - db := getTestDB() - _, err := zremrangebyrankCommand(db, "test_zremrangebyrank") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zremrangebyrank") { - t.Fatal("invalid err ", err) - } - v, err := zremrangebyrankCommand(db, "test_zremrangebyrank", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZRemrangebyscore(t *testing.T) { - db := getTestDB() - _, err := zremrangebyscoreCommand(db, "test_zremrangebyscore") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zremrangebyscore") { - t.Fatal("invalid err ", err) - } - v, err := zremrangebyscoreCommand(db, "test_zremrangebyscore", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZRangeCommand(t *testing.T) { - db := getTestDB() - _, err := zrangeCommand(db, "test_zrange") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrange") { - t.Fatal("invalid err ", err) - } - v, err := zrangeCommand(db, "test_zrange", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if len(v.([]string)) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZRangebyscoreCommand(t *testing.T) { - db := getTestDB() - _, err := zrangebyscoreCommand(db, "test_zrangebyscore") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrangebyscore") { - t.Fatal("invalid err ", err) - } - v, err := zrangebyscoreCommand(db, "test_zrangebyscore", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if len(v.([]string)) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZRevrangebyscoreCommand(t *testing.T) { - db := getTestDB() - _, err := zrevrangebyscoreCommand(db, "test_zrevrangebyscore") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zrevrangebyscore") { - t.Fatal("invalid err ", err) - } - v, err := zrevrangebyscoreCommand(db, "test_zrevrangebyscore", "0", "1") - if err != nil { - t.Fatal(err.Error()) - } - - if len(v.([]string)) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZMclearCommand(t *testing.T) { - db := getTestDB() - _, err := zmclearCommand(db) - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zmclear") { - t.Fatal("invalid err ", err) - } - v, err := zmclearCommand(db, "test_zmclear") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 1 { - t.Fatal("invalid result ", v) - } -} - -func TestZExpireCommand(t *testing.T) { - db := getTestDB() - _, err := zexpireCommand(db, "test_zexpire") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zexpire") { - t.Fatal("invalid err ", err) - } - v, err := zexpireCommand(db, "test_zexpire", "10") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZExpireAtCommand(t *testing.T) { - db := getTestDB() - _, err := zexpireAtCommand(db, "test_zexpireat") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zexpireat") { - t.Fatal("invalid err ", err) - } - expireAt := fmt.Sprintf("%d", time.Now().Unix()+10) - v, err := zexpireAtCommand(db, "test_zexpire", expireAt) - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} - -func TestZTTLCommand(t *testing.T) { - db := getTestDB() - _, err := zttlCommand(db, "test_zttl", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zttl") { - t.Fatal("invalid err ", err) - } - v, err := zttlCommand(db, "test_zttl") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != -1 { - t.Fatal("invalid result ", v) - } -} - -func TestZPersistCommand(t *testing.T) { - db := getTestDB() - _, err := zpersistCommand(db, "test_zpersist", "a") - if err == nil || err.Error() != fmt.Sprintf(ERR_ARGUMENT_FORMAT, "zpersist") { - t.Fatal("invalid err ", err) - } - v, err := zpersistCommand(db, "test_zpersist") - if err != nil { - t.Fatal(err.Error()) - } - - if v.(int64) != 0 { - t.Fatal("invalid result ", v) - } -} diff --git a/server/http/handler.go b/server/http/handler.go deleted file mode 100644 index b958054..0000000 --- a/server/http/handler.go +++ /dev/null @@ -1,156 +0,0 @@ -package http - -import ( - "net/http" - //"github.com/siddontang/go-websocket/websocket" - "encoding/json" - "fmt" - "github.com/siddontang/go-log/log" - "github.com/siddontang/ledisdb/ledis" - "github.com/ugorji/go/codec" - "gopkg.in/mgo.v2/bson" - "strconv" - "strings" -) - -type CmdHandler struct { - Ldb *ledis.Ledis -} - -var allowedContentTypes = map[string]struct{}{ - "json": struct{}{}, - "bson": struct{}{}, - "msgpack": struct{}{}, -} - -func (h *CmdHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - w.WriteHeader(http.StatusForbidden) - return - } - - idx, cmd, args := h.parseReqPath(r.URL.Path) - - contentType := r.FormValue("type") - if contentType == "" { - contentType = "json" - } - contentType = strings.ToLower(contentType) - if _, ok := allowedContentTypes[contentType]; !ok { - h.writeError( - cmd, - fmt.Errorf("unsupported content type '%s', only json, bson, msgpack are supported", contentType), - w, - "json") - return - } - cmdFunc := lookup(cmd) - if cmdFunc == nil { - h.cmdNotFound(cmd, w, contentType) - return - } - var db *ledis.DB - var err error - if db, err = h.Ldb.Select(idx); err != nil { - h.writeError(cmd, err, w, contentType) - return - } - result, err := cmdFunc(db, args...) - if err != nil { - h.writeError(cmd, err, w, contentType) - return - } - h.write(cmd, result, w, contentType) -} - -func (h *CmdHandler) parseReqPath(path string) (db int, cmd string, args []string) { - /* - this function extracts `db`, `cmd` and `args` from `path` - the proper format of `path` is /cmd/arg1/arg2/../argN or /db/cmd/arg1/arg2/../argN - if `path` is the first kind, `db` will be 0 - */ - substrings := strings.Split(strings.TrimLeft(path, "/"), "/") - if len(substrings) == 1 { - return 0, substrings[0], substrings[1:] - } - db, err := strconv.Atoi(substrings[0]) - if err != nil { - cmd = substrings[0] - args = substrings[1:] - } else { - cmd = substrings[1] - args = substrings[2:] - } - return -} -func (h *CmdHandler) cmdNotFound(cmd string, w http.ResponseWriter, contentType string) { - err := fmt.Errorf("unknown command '%s'", cmd) - h.writeError(cmd, err, w, contentType) -} - -func (h *CmdHandler) write(cmd string, result interface{}, w http.ResponseWriter, contentType string) { - m := map[string]interface{}{ - cmd: result, - } - - switch contentType { - case "json": - writeJSON(&m, w) - case "bson": - writeBSON(&m, w) - case "msgpack": - writeMsgPack(&m, w) - default: - log.Error("invalid content type %s", contentType) - } -} - -func (h *CmdHandler) writeError(cmd string, err error, w http.ResponseWriter, contentType string) { - result := [2]interface{}{ - false, - fmt.Sprintf("ERR %s", err.Error()), - } - h.write(cmd, result, w, contentType) -} - -func writeJSON(resutl interface{}, w http.ResponseWriter) { - buf, err := json.Marshal(resutl) - if err != nil { - log.Error(err.Error()) - return - } - - w.Header().Set("Content-type", "application/json; charset=utf-8") - w.Header().Set("Content-Length", strconv.Itoa(len(buf))) - - _, err = w.Write(buf) - if err != nil { - log.Error(err.Error()) - } -} - -func writeBSON(result interface{}, w http.ResponseWriter) { - buf, err := bson.Marshal(result) - if err != nil { - log.Error(err.Error()) - return - } - - w.Header().Set("Content-type", "application/octet-stream") - w.Header().Set("Content-Length", strconv.Itoa(len(buf))) - - _, err = w.Write(buf) - if err != nil { - log.Error(err.Error()) - } -} - -func writeMsgPack(result interface{}, w http.ResponseWriter) { - w.Header().Set("Content-type", "application/octet-stream") - - var mh codec.MsgpackHandle - enc := codec.NewEncoder(w, &mh) - if err := enc.Encode(result); err != nil { - log.Error(err.Error()) - } -} diff --git a/server/http/http_io.go b/server/http/http_io.go deleted file mode 100644 index 013e9b8..0000000 --- a/server/http/http_io.go +++ /dev/null @@ -1,95 +0,0 @@ -package http - -import ( - "github.com/siddontang/ledisdb/ledis" - "io" - "net/http" -) - -type httpContext struct { -} - -type httpReader struct { - req *http.Request -} - -type httpWriter struct { - resp *http.ResponseWriter -} - -// http context - -func newHttpContext() *httpContext { - ctx := new(httpContext) - return ctx -} - -func (ctx *httpContext) addr() string { - - return "" -} - -func (ctx *httpContext) release() { - -} - -// http reader - -func newHttpReader(req *http.Request) *httpReader { - r := new(httpReader) - r.req = req - return r -} - -func (r *httpReader) read() ([][]byte, error) { - - return nil, nil -} - -// http writer - -func newHttpWriter(resp *http.ResponseWriter) *httpWriter { - w := new(httpWriter) - w.resp = resp - return w -} - -func (w *httpWriter) writeError(err error) { - -} - -func (w *httpWriter) writeStatus(status string) { - -} - -func (w *httpWriter) writeInteger(n int64) { - -} - -func (w *httpWriter) writeBulk(b []byte) { - -} - -func (w *httpWriter) writeArray(lst []interface{}) { - -} - -func (w *httpWriter) writeSliceArray(lst [][]byte) { - -} - -func (w *httpWriter) writeFVPairArray(lst []ledis.FVPair) { - -} - -func (w *httpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) { - -} - -func (w *httpWriter) writeBulkFrom(n int64, rb io.Reader) { - -} - -func (w *httpWriter) flush() { - -} diff --git a/server/http/readme.md b/server/http/readme.md deleted file mode 100644 index 6e05c43..0000000 --- a/server/http/readme.md +++ /dev/null @@ -1,42 +0,0 @@ -##HTTP Interface -LedisDB provides http interfaces for most commands. -####Request -The proper url format is - - http://host:port[/db]/cmd/arg1/arg2/.../argN[?type=type] - -'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a custom content type, can be json, bson or msgpack, json is default. - - -####Response - -The response format is - - { cmd: return_value } - -or - - { cmd: [success, message] } - -'return_value' stands for the output of 'cmd', it can be a number, a string, a list, or a hash. If the return value is just a descriptive message, the second format will be taken, and 'success', a boolean value, indicates whether it is successful. - -####Example -#####Curl - - curl http://127.0.0.1:11181/SET/hello/world - → {"SET":[true,"OK"]} - - curl http://127.0.0.1:11181/0/GET/hello?type=json - → {"GET":"world"} - -#####Python -Requires [msgpack-python](https://pypi.python.org/pypi/msgpack-python) and [requests](https://pypi.python.org/pypi/requests/) - - >>> import requests - >>> import msgpack - - >>> requests.get("http://127.0.0.1:11181/0/SET/hello/world") - >>> r = requests.get("http://127.0.0.1:11181/0/GET/hello?type=msgpack") - >>> msgpack.unpackb(r.content) - >>> {"GET":"world"} - From 2166c6ff1954fb03ad93c86f1c72b2e3251c9a7f Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 11:59:16 +0800 Subject: [PATCH 25/51] add a extra bool value when returning status --- server/client_http.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/client_http.go b/server/client_http.go index a2ae729..f31c4df 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -147,7 +147,11 @@ func (w *httpWriter) writeError(err error) { } func (w *httpWriter) writeStatus(status string) { - w.genericWrite(status) + var success bool + if status == OK || status == PONG { + success = true + } + w.genericWrite([]interface{}{success, status}) } func (w *httpWriter) writeInteger(n int64) { From b5e5b787d6e81f668ffeb15d26695240294b36b3 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 14:18:07 +0800 Subject: [PATCH 26/51] modify argument checking --- server/cmd_bit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/cmd_bit.go b/server/cmd_bit.go index d95e416..a7b9d1a 100644 --- a/server/cmd_bit.go +++ b/server/cmd_bit.go @@ -210,7 +210,7 @@ func bexpireCommand(req *requestContext) error { func bexpireatCommand(req *requestContext) error { args := req.args - if len(args) == 0 { + if len(args) != 1 { return ErrCmdParams } @@ -230,7 +230,7 @@ func bexpireatCommand(req *requestContext) error { func bttlCommand(req *requestContext) error { args := req.args - if len(args) == 0 { + if len(args) != 1 { return ErrCmdParams } From 95b6dd88fabe84f1d4956540964a109abc223e4e Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 14:27:34 +0800 Subject: [PATCH 27/51] ignorecase --- server/client_http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/client_http.go b/server/client_http.go index f31c4df..2e36572 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -89,7 +89,7 @@ func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWrite args[i] = []byte(arg) } - req.cmd = cmd + req.cmd = strings.ToLower(cmd) req.args = args req.remoteAddr = c.addr(r) req.resp = &httpWriter{contentType, cmd, w} From 33a2e6afb932a01f4365876dbb353963dfc3ff25 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 15:01:16 +0800 Subject: [PATCH 28/51] http interface: not support cmds of repl --- server/client_http.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/client_http.go b/server/client_http.go index 2e36572..ed1b7b9 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -35,8 +35,6 @@ type httpWriter struct { w http.ResponseWriter } -// http context - func newClientHTTP(app *App, w http.ResponseWriter, r *http.Request) { var err error c := new(httpClient) @@ -90,7 +88,11 @@ func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWrite } req.cmd = strings.ToLower(cmd) - req.args = args + + if req.cmd == "slaveof" || req.cmd == "fullsync" || req.cmd == "sync" { + return nil, fmt.Errorf("unsupported command: '%s'", cmd) + } + req.remoteAddr = c.addr(r) req.resp = &httpWriter{contentType, cmd, w} return req, nil From 6947255c22b9d8a238b1c90ccb70d2c2cafdb388 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 15:22:24 +0800 Subject: [PATCH 29/51] put args into reqContext --- server/client_http.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/client_http.go b/server/client_http.go index ed1b7b9..a99b62c 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -92,6 +92,7 @@ func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWrite if req.cmd == "slaveof" || req.cmd == "fullsync" || req.cmd == "sync" { return nil, fmt.Errorf("unsupported command: '%s'", cmd) } + req.args = args req.remoteAddr = c.addr(r) req.resp = &httpWriter{contentType, cmd, w} From 3b89543de69c623e76f3b00e263b1198bef0fb91 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Fri, 1 Aug 2014 16:03:51 +0800 Subject: [PATCH 30/51] fix argument checking --- server/cmd_zset.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/server/cmd_zset.go b/server/cmd_zset.go index d2facd3..01bca01 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -121,16 +121,17 @@ func zparseScoreRange(minBuf []byte, maxBuf []byte) (min int64, max int64, err e min = math.MinInt64 } else { var lopen bool = false - if minBuf[0] == '(' { - lopen = true - minBuf = minBuf[1:] - } if len(minBuf) == 0 { err = ErrCmdParams return } + if minBuf[0] == '(' { + lopen = true + minBuf = minBuf[1:] + } + min, err = ledis.StrInt64(minBuf, nil) if err != nil { return @@ -150,15 +151,15 @@ func zparseScoreRange(minBuf []byte, maxBuf []byte) (min int64, max int64, err e max = math.MaxInt64 } else { var ropen = false - if maxBuf[0] == '(' { - ropen = true - maxBuf = maxBuf[1:] - } if len(maxBuf) == 0 { err = ErrCmdParams return } + if maxBuf[0] == '(' { + ropen = true + maxBuf = maxBuf[1:] + } max, err = ledis.StrInt64(maxBuf, nil) if err != nil { From 9a241194fd24072692891458864d27bc9dec0dc3 Mon Sep 17 00:00:00 2001 From: siddontang Date: Sat, 2 Aug 2014 15:26:33 +0800 Subject: [PATCH 31/51] update bolt info --- README.md | 2 +- bootstrap.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a738700..4ad7efd 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ LedisDB now supports multi database as backend to store data, you can test and c + Rich advanced data structure: KV, List, Hash, ZSet, Bit. + Stores lots of data, over the memory limit. -+ Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB. ++ Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB, BoltDB. + Supports expiration and ttl. + Redis clients, like redis-cli, are supported directly. + Multi client API supports, including Golang, Python, Lua(Openresty). diff --git a/bootstrap.sh b/bootstrap.sh index 6dd225f..6772133 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -9,3 +9,5 @@ go get github.com/siddontang/copier go get github.com/siddontang/goleveldb/leveldb go get -d github.com/siddontang/gomdb + +go get github.com/boltdb/bolt \ No newline at end of file From cdbb351ab88c68ec2332a29873fb7a783d017157 Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 4 Aug 2014 08:48:06 +0800 Subject: [PATCH 32/51] use szferi gomdb --- bootstrap.sh | 2 +- store/mdb/mdb.go | 20 ++++++++++---------- store/mdb/tx.go | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 6772133..6440466 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -8,6 +8,6 @@ go get github.com/siddontang/copier go get github.com/siddontang/goleveldb/leveldb -go get -d github.com/siddontang/gomdb +go get github.com/szferi/gomdb go get github.com/boltdb/bolt \ No newline at end of file diff --git a/store/mdb/mdb.go b/store/mdb/mdb.go index 88c4856..5d3de57 100644 --- a/store/mdb/mdb.go +++ b/store/mdb/mdb.go @@ -1,8 +1,8 @@ package mdb import ( - mdb "github.com/siddontang/gomdb" "github.com/siddontang/ledisdb/store/driver" + mdb "github.com/szferi/gomdb" "os" ) @@ -20,7 +20,7 @@ type MDB struct { func Open(c *Config) (MDB, error) { path := c.Path if c.MapSize == 0 { - c.MapSize = 1024 * 1024 * 1024 + c.MapSize = 500 * 1024 * 1024 } env, err := mdb.NewEnv() @@ -90,7 +90,7 @@ func (db MDB) BatchPut(writes []driver.Write) error { for _, w := range writes { if w.Value == nil { - itr.key, itr.value, itr.err = itr.c.Get(w.Key, mdb.SET) + itr.key, itr.value, itr.err = itr.c.Get(w.Key, nil, mdb.SET) if itr.err == nil { itr.err = itr.c.Del(0) } @@ -125,7 +125,7 @@ func (db MDB) Delete(key []byte) error { itr := db.iterator(false) defer itr.Close() - itr.key, itr.value, itr.err = itr.c.Get(key, mdb.SET) + itr.key, itr.value, itr.err = itr.c.Get(key, nil, mdb.SET) if itr.err == nil { itr.err = itr.c.Del(0) } @@ -161,31 +161,31 @@ func (itr *MDBIterator) Error() error { } func (itr *MDBIterator) getCurrent() { - itr.key, itr.value, itr.err = itr.c.Get(nil, mdb.GET_CURRENT) + itr.key, itr.value, itr.err = itr.c.Get(nil, nil, mdb.GET_CURRENT) itr.setState() } func (itr *MDBIterator) Seek(key []byte) { - itr.key, itr.value, itr.err = itr.c.Get(key, mdb.SET_RANGE) + itr.key, itr.value, itr.err = itr.c.Get(key, nil, mdb.SET_RANGE) itr.setState() } func (itr *MDBIterator) Next() { - itr.key, itr.value, itr.err = itr.c.Get(nil, mdb.NEXT) + itr.key, itr.value, itr.err = itr.c.Get(nil, nil, mdb.NEXT) itr.setState() } func (itr *MDBIterator) Prev() { - itr.key, itr.value, itr.err = itr.c.Get(nil, mdb.PREV) + itr.key, itr.value, itr.err = itr.c.Get(nil, nil, mdb.PREV) itr.setState() } func (itr *MDBIterator) First() { - itr.key, itr.value, itr.err = itr.c.Get(nil, mdb.FIRST) + itr.key, itr.value, itr.err = itr.c.Get(nil, nil, mdb.FIRST) itr.setState() } func (itr *MDBIterator) Last() { - itr.key, itr.value, itr.err = itr.c.Get(nil, mdb.LAST) + itr.key, itr.value, itr.err = itr.c.Get(nil, nil, mdb.LAST) itr.setState() } diff --git a/store/mdb/tx.go b/store/mdb/tx.go index b78f488..0946727 100644 --- a/store/mdb/tx.go +++ b/store/mdb/tx.go @@ -1,8 +1,8 @@ package mdb import ( - mdb "github.com/siddontang/gomdb" "github.com/siddontang/ledisdb/store/driver" + mdb "github.com/szferi/gomdb" ) type Tx struct { @@ -53,7 +53,7 @@ func (t *Tx) BatchPut(writes []driver.Write) error { for _, w := range writes { if w.Value == nil { - itr.key, itr.value, itr.err = itr.c.Get(w.Key, mdb.SET) + itr.key, itr.value, itr.err = itr.c.Get(w.Key, nil, mdb.SET) if itr.err == nil { itr.err = itr.c.Del(0) } From 9c5ade8ee3ff7718594b5fc1bdbdbe23d0f5dac8 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 09:44:58 +0800 Subject: [PATCH 33/51] add document: http_interface.md --- server/http_interface.md | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 server/http_interface.md diff --git a/server/http_interface.md b/server/http_interface.md new file mode 100644 index 0000000..6e05c43 --- /dev/null +++ b/server/http_interface.md @@ -0,0 +1,42 @@ +##HTTP Interface +LedisDB provides http interfaces for most commands. +####Request +The proper url format is + + http://host:port[/db]/cmd/arg1/arg2/.../argN[?type=type] + +'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a custom content type, can be json, bson or msgpack, json is default. + + +####Response + +The response format is + + { cmd: return_value } + +or + + { cmd: [success, message] } + +'return_value' stands for the output of 'cmd', it can be a number, a string, a list, or a hash. If the return value is just a descriptive message, the second format will be taken, and 'success', a boolean value, indicates whether it is successful. + +####Example +#####Curl + + curl http://127.0.0.1:11181/SET/hello/world + → {"SET":[true,"OK"]} + + curl http://127.0.0.1:11181/0/GET/hello?type=json + → {"GET":"world"} + +#####Python +Requires [msgpack-python](https://pypi.python.org/pypi/msgpack-python) and [requests](https://pypi.python.org/pypi/requests/) + + >>> import requests + >>> import msgpack + + >>> requests.get("http://127.0.0.1:11181/0/SET/hello/world") + >>> r = requests.get("http://127.0.0.1:11181/0/GET/hello?type=msgpack") + >>> msgpack.unpackb(r.content) + >>> {"GET":"world"} + From 9ad7095e2d879ad89ce728b4b2d1413c8f103401 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 10:17:30 +0800 Subject: [PATCH 34/51] modify accesslog format --- server/request.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/server/request.go b/server/request.go index 5c80284..0531a37 100644 --- a/server/request.go +++ b/server/request.go @@ -107,12 +107,9 @@ func (req *requestContext) catGenericCommand() []byte { buffer.Write([]byte(req.cmd)) - nargs := len(req.args) - for i, arg := range req.args { + for _, arg := range req.args { + buffer.WriteByte(' ') buffer.Write(arg) - if i != nargs-1 { - buffer.WriteByte(' ') - } } return buffer.Bytes() From 1655f48652c98682fb82f1827f121df427cfc48f Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 10:28:09 +0800 Subject: [PATCH 35/51] modify remote addr --- server/client_http.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/server/client_http.go b/server/client_http.go index a99b62c..db4595d 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -19,6 +19,9 @@ var allowedContentTypes = map[string]struct{}{ "bson": struct{}{}, "msgpack": struct{}{}, } +var unsopportedCommands = map[string]struct{}{ + "": struct{}{}, +} type httpClient struct { app *App @@ -55,14 +58,7 @@ func newClientHTTP(app *App, w http.ResponseWriter, r *http.Request) { } func (c *httpClient) addr(r *http.Request) string { - addr := r.Header.Get("X-Forwarded-For") - if addr == "" { - addr = r.Header.Get("X-Real-IP") - if addr == "" { - addr = r.Header.Get("Remote-Addr") - } - } - return addr + return r.RemoteAddr } func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWriter) (*requestContext, error) { From 93761d8ff3648ed05946ae42f510ab4f3f8a34f7 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 10:38:43 +0800 Subject: [PATCH 36/51] http interface: unsupport repl cmds --- server/client_http.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/client_http.go b/server/client_http.go index db4595d..b37700e 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -20,7 +20,9 @@ var allowedContentTypes = map[string]struct{}{ "msgpack": struct{}{}, } var unsopportedCommands = map[string]struct{}{ - "": struct{}{}, + "slaveof": struct{}{}, + "fullsync": struct{}{}, + "sync": struct{}{}, } type httpClient struct { @@ -84,10 +86,10 @@ func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWrite } req.cmd = strings.ToLower(cmd) - - if req.cmd == "slaveof" || req.cmd == "fullsync" || req.cmd == "sync" { + if _, ok := unsopportedCommands[req.cmd]; ok { return nil, fmt.Errorf("unsupported command: '%s'", cmd) } + req.args = args req.remoteAddr = c.addr(r) From 828b74e2d584d2a4a61b334d4b3597bcf9291726 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 10:47:10 +0800 Subject: [PATCH 37/51] update doc.go --- server/doc.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/doc.go b/server/doc.go index a283cab..f1ce245 100644 --- a/server/doc.go +++ b/server/doc.go @@ -26,4 +26,13 @@ // // After you send slaveof command, the slave will start to sync master's binlog and replicate from binlog. // +// HTTP Interface +// LedisDB provides http interfaces for most commands(except the replication commands) +// +// curl http://127.0.0.1:11181/SET/hello/world +// → {"SET":[true,"OK"]} +// +// curl http://127.0.0.1:11181/0/GET/hello?type=json +// → {"GET":"world"} +// package server From 6c88687ebf611b0f79a788adc9b3372509bb30e4 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 11:06:28 +0800 Subject: [PATCH 38/51] add quit commands --- server/client_http.go | 1 + server/client_resp.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/server/client_http.go b/server/client_http.go index b37700e..9a4057b 100644 --- a/server/client_http.go +++ b/server/client_http.go @@ -23,6 +23,7 @@ var unsopportedCommands = map[string]struct{}{ "slaveof": struct{}{}, "fullsync": struct{}{}, "sync": struct{}{}, + "quit": struct{}{}, } type httpClient struct { diff --git a/server/client_resp.go b/server/client_resp.go index 5252c6a..1457b81 100644 --- a/server/client_resp.go +++ b/server/client_resp.go @@ -138,6 +138,11 @@ func (c *respClient) handleRequest(reqData [][]byte) { c.req.cmd = strings.ToLower(ledis.String(reqData[0])) c.req.args = reqData[1:] } + if c.req.cmd == "quit" { + c.req.resp.writeStatus(OK) + c.req.resp.flush() + c.conn.Close() + } req.db = c.db From ab189ffe407900f10219e2eeaa4a839362d7f031 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 11:37:21 +0800 Subject: [PATCH 39/51] add return statement --- server/client_resp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/client_resp.go b/server/client_resp.go index 1457b81..e89ad96 100644 --- a/server/client_resp.go +++ b/server/client_resp.go @@ -142,6 +142,7 @@ func (c *respClient) handleRequest(reqData [][]byte) { c.req.resp.writeStatus(OK) c.req.resp.flush() c.conn.Close() + return } req.db = c.db From 6c10cca3eb1602f582c6293e1c5138864f61f4fd Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 11:55:39 +0800 Subject: [PATCH 40/51] update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 4ad7efd..97fb9cd 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,14 @@ You must known that changing store database runtime is very dangerous, LedisDB w ledis 127.0.0.1:6380> get a "1" + //use curl + curl http://127.0.0.1:11181/SET/hello/world + → {"SET":[true,"OK"]} + + curl http://127.0.0.1:11181/0/GET/hello?type=json + → {"GET":"world"} + + ## Package Example import "github.com/siddontang/ledisdb/ledis" From 7485899c174026bfb2b29d8fc9c3614d1c3d2837 Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 4 Aug 2014 14:13:35 +0800 Subject: [PATCH 41/51] update doc --- README.md | 5 +++-- server/doc.go | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 97fb9cd..84ccb00 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ # LedisDB -Ledisdb is a high performance NoSQL like Redis written by go. It supports some advanced data structure like kv, list, hash and zset, and may be alternative for Redis. +Ledisdb is a high performance NoSQL like Redis written by go. It supports some advanced data structure like kv, list, hash, zset, bitmap, and may be alternative for Redis. LedisDB now supports multi database as backend to store data, you can test and choose the proper one for you. ## Features -+ Rich advanced data structure: KV, List, Hash, ZSet, Bit. ++ Rich advanced data structure: KV, List, Hash, ZSet, Bitmap. + Stores lots of data, over the memory limit. + Various backend database to use: LevelDB, goleveldb, LMDB, RocksDB, BoltDB. + Supports expiration and ttl. + Redis clients, like redis-cli, are supported directly. + Multi client API supports, including Golang, Python, Lua(Openresty). + Easy to embed in Golang application. ++ Restful API support. + Replication to guarantee data safe. + Supplies tools to load, dump, repair database. diff --git a/server/doc.go b/server/doc.go index f1ce245..75714f2 100644 --- a/server/doc.go +++ b/server/doc.go @@ -27,12 +27,13 @@ // After you send slaveof command, the slave will start to sync master's binlog and replicate from binlog. // // HTTP Interface +// // LedisDB provides http interfaces for most commands(except the replication commands) // -// curl http://127.0.0.1:11181/SET/hello/world -// → {"SET":[true,"OK"]} +// curl http://127.0.0.1:11181/SET/hello/world +// → {"SET":[true,"OK"]} // -// curl http://127.0.0.1:11181/0/GET/hello?type=json -// → {"GET":"world"} +// curl http://127.0.0.1:11181/0/GET/hello?type=json +// → {"GET":"world"} // package server From 8c9ddb72eaec6aa067959973337c5ce287fccd56 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Mon, 4 Aug 2014 14:37:06 +0800 Subject: [PATCH 42/51] Delete http_interface.md --- server/http_interface.md | 42 ---------------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 server/http_interface.md diff --git a/server/http_interface.md b/server/http_interface.md deleted file mode 100644 index 6e05c43..0000000 --- a/server/http_interface.md +++ /dev/null @@ -1,42 +0,0 @@ -##HTTP Interface -LedisDB provides http interfaces for most commands. -####Request -The proper url format is - - http://host:port[/db]/cmd/arg1/arg2/.../argN[?type=type] - -'db' and 'type' are optional. 'db' stands for ledis db index, ranges from 0 to 15, its default value is 0. 'type' is a custom content type, can be json, bson or msgpack, json is default. - - -####Response - -The response format is - - { cmd: return_value } - -or - - { cmd: [success, message] } - -'return_value' stands for the output of 'cmd', it can be a number, a string, a list, or a hash. If the return value is just a descriptive message, the second format will be taken, and 'success', a boolean value, indicates whether it is successful. - -####Example -#####Curl - - curl http://127.0.0.1:11181/SET/hello/world - → {"SET":[true,"OK"]} - - curl http://127.0.0.1:11181/0/GET/hello?type=json - → {"GET":"world"} - -#####Python -Requires [msgpack-python](https://pypi.python.org/pypi/msgpack-python) and [requests](https://pypi.python.org/pypi/requests/) - - >>> import requests - >>> import msgpack - - >>> requests.get("http://127.0.0.1:11181/0/SET/hello/world") - >>> r = requests.get("http://127.0.0.1:11181/0/GET/hello?type=msgpack") - >>> msgpack.unpackb(r.content) - >>> {"GET":"world"} - From 0bcbc0a0d1da8e27d1220a6d508bb22a9bf99f16 Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 4 Aug 2014 15:25:42 +0800 Subject: [PATCH 43/51] update bootstrap.sh --- bootstrap.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index e4c7a5b..114d15f 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -2,16 +2,16 @@ . ./dev.sh -go get -u github.com/siddontang/go-log/log -go get -u github.com/siddontang/go-snappy/snappy -go get -u github.com/siddontang/copier +go get github.com/siddontang/go-log/log +go get github.com/siddontang/go-snappy/snappy +go get github.com/siddontang/copier -go get -u github.com/siddontang/goleveldb/leveldb +go get github.com/siddontang/goleveldb/leveldb -go get -u github.com/szferi/gomdb +go get github.com/szferi/gomdb -go get -u github.com/boltdb/bolt +go get github.com/boltdb/bolt -go get -u gopkg.in/mgo.v2/bson -go get -u github.com/ugorji/go/codec +go get gopkg.in/mgo.v2/bson +go get github.com/ugorji/go/codec From a5c27a1e23f35b93245f59a9a0a19805aa6904df Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 4 Aug 2014 15:36:05 +0800 Subject: [PATCH 44/51] lmdb can be build in windows --- store/mdb.go | 2 -- store/mdb_test.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/store/mdb.go b/store/mdb.go index ffbf010..364065a 100644 --- a/store/mdb.go +++ b/store/mdb.go @@ -1,5 +1,3 @@ -// +build !windows - package store import ( diff --git a/store/mdb_test.go b/store/mdb_test.go index 512b375..18357d5 100644 --- a/store/mdb_test.go +++ b/store/mdb_test.go @@ -1,5 +1,3 @@ -// +build !windows - package store import ( From c058a17b2ce2f62f5b837b852ba7dfc3171eb505 Mon Sep 17 00:00:00 2001 From: siddontang Date: Mon, 4 Aug 2014 16:29:10 +0800 Subject: [PATCH 45/51] update read me --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84ccb00..694a082 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ LedisDB now supports multi database as backend to store data, you can test and c + Redis clients, like redis-cli, are supported directly. + Multi client API supports, including Golang, Python, Lua(Openresty). + Easy to embed in Golang application. -+ Restful API support. ++ Restful API support, json/bson/msgpack output. + Replication to guarantee data safe. + Supplies tools to load, dump, repair database. From 47413340e1c865a83d68b058a136cc5e10f00508 Mon Sep 17 00:00:00 2001 From: siddontang Date: Tue, 5 Aug 2014 08:43:04 +0800 Subject: [PATCH 46/51] add net http pprof --- cmd/ledis-server/main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/ledis-server/main.go b/cmd/ledis-server/main.go index 576ab7f..8673cd2 100644 --- a/cmd/ledis-server/main.go +++ b/cmd/ledis-server/main.go @@ -3,6 +3,9 @@ package main import ( "flag" "github.com/siddontang/ledisdb/server" + "log" + "net/http" + _ "net/http/pprof" "os" "os/signal" "runtime" @@ -52,5 +55,9 @@ func main() { app.Close() }() + go func() { + log.Println(http.ListenAndServe("localhost:6060", nil)) + }() + app.Run() } From ea2e2754ca7ad08ec52b295ed9997d1d0a38546b Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 5 Aug 2014 10:52:39 +0800 Subject: [PATCH 47/51] add node.js ledis client --- client/nodejs/README.md | 77 ++ client/nodejs/example.js | 38 + client/nodejs/ledis/.npmignore | 8 + client/nodejs/ledis/connection_breaker.js | 80 ++ client/nodejs/ledis/index.js | 1245 ++++++++++++++++++ client/nodejs/ledis/lib/commands.js | 99 ++ client/nodejs/ledis/lib/parser/hiredis.js | 46 + client/nodejs/ledis/lib/parser/javascript.js | 301 +++++ client/nodejs/ledis/lib/queue.js | 59 + client/nodejs/ledis/lib/to_array.js | 12 + client/nodejs/ledis/lib/util.js | 11 + client/nodejs/ledis/package.json | 15 + 12 files changed, 1991 insertions(+) create mode 100644 client/nodejs/README.md create mode 100644 client/nodejs/example.js create mode 100644 client/nodejs/ledis/.npmignore create mode 100644 client/nodejs/ledis/connection_breaker.js create mode 100644 client/nodejs/ledis/index.js create mode 100644 client/nodejs/ledis/lib/commands.js create mode 100644 client/nodejs/ledis/lib/parser/hiredis.js create mode 100644 client/nodejs/ledis/lib/parser/javascript.js create mode 100644 client/nodejs/ledis/lib/queue.js create mode 100644 client/nodejs/ledis/lib/to_array.js create mode 100644 client/nodejs/ledis/lib/util.js create mode 100644 client/nodejs/ledis/package.json diff --git a/client/nodejs/README.md b/client/nodejs/README.md new file mode 100644 index 0000000..4fa6560 --- /dev/null +++ b/client/nodejs/README.md @@ -0,0 +1,77 @@ +###Ledis - a node.js LedisDB client +This is a modification of [simplegeo/nodejs-redis](https://github.com/simplegeo/nodejs-redis) , aiming to be compatible with LedisDB. + +###Setup +Just copy (or move) the ledis directory into your project's **node_modules** directory. + + cp -r /path/to/github.com/src/ledisdb/client/nodejs/ledis /path/to/your/node_modules/ + +###Example +Below is the total content of example.js, including the ledisDB's special commands. + + + var ledis = require("ledis"), + client = ledis.createClient(); + + client.on("error", function (err) { + console.log("Error " + err); + }); + + client.set("string key", "string val", ledis.print); + client.get("string key", ledis.print); + client.hset("hash key", "hashtest 1", "some value", ledis.print); + client.hset(["hash key", "hashtest 2", "some other value"], ledis.print); + client.hkeys("hash key", function (err, replies) { + console.log(replies.length + " replies:"); + replies.forEach(function (reply, i) { + console.log(" " + i + ": " + reply); + }); + }); + + //ledis special commands + client.lpush("list key", "1", "2", "3", ledis.print); + client.lrange("list key", "0", "2", ledis.print); + client.lclear("list key", ledis.print); + client.lrange("list key", "0", "2", ledis.print); + + client.zadd("zset key", 100, "m", ledis.print); + client.zexpire("zset key", 40, ledis.print); + client.zttl("zset key", ledis.print); + + client.bsetbit("bit key 1", 1, 1, ledis.print); + client.bsetbit("bit key 2", 1, 1, ledis.print); + client.bopt("and", "bit key 3", "bit key 1", "bit key 2", ledis.print); + client.bget("bit key 3", function(err, result){ + if (result=="\x02"){ + console.log("Reply: \\x02") + } + }); + + client.quit(); + +Run the example in your project directory, and will display + + wyk:~/my/project/dir/$ node example.js + + Reply: OK + Reply: string val + Reply: 0 + Reply: 0 + 2 replies: + 0: hashtest 1 + 1: hashtest 2 + Reply: 3 + Reply: 3,2,1 + Reply: 3 + Reply: + Reply: 1 + Reply: 1 + Reply: 40 + Reply: 1 + Reply: 1 + Reply: 2 + Reply: \x02 + + + + diff --git a/client/nodejs/example.js b/client/nodejs/example.js new file mode 100644 index 0000000..f28c50a --- /dev/null +++ b/client/nodejs/example.js @@ -0,0 +1,38 @@ +var ledis = require("ledis"), + client = ledis.createClient(); + +client.on("error", function (err) { + console.log("Error " + err); +}); + +client.set("string key", "string val", ledis.print); +client.get("string key", ledis.print); +client.hset("hash key", "hashtest 1", "some value", ledis.print); +client.hset(["hash key", "hashtest 2", "some other value"], ledis.print); +client.hkeys("hash key", function (err, replies) { + console.log(replies.length + " replies:"); + replies.forEach(function (reply, i) { + console.log(" " + i + ": " + reply); + }); +}); + +//ledis special commands +client.lpush("list key", "1", "2", "3", ledis.print); +client.lrange("list key", "0", "2", ledis.print); +client.lclear("list key", ledis.print); +client.lrange("list key", "0", "2", ledis.print); + +client.zadd("zset key", 100, "m", ledis.print); +client.zexpire("zset key", 40, ledis.print); +client.zttl("zset key", ledis.print); + +client.bsetbit("bit key 1", 1, 1, ledis.print); +client.bsetbit("bit key 2", 1, 1, ledis.print); +client.bopt("and", "bit key 3", "bit key 1", "bit key 2", ledis.print); +client.bget("bit key 3", function(err, result){ + if (result=="\x02"){ + console.log("Reply: \\x02") + } +}); + +client.quit(); diff --git a/client/nodejs/ledis/.npmignore b/client/nodejs/ledis/.npmignore new file mode 100644 index 0000000..61755a6 --- /dev/null +++ b/client/nodejs/ledis/.npmignore @@ -0,0 +1,8 @@ +examples/ +benches/ +test.js +diff_multi_bench_output.js +generate_commands.js +multi_bench.js +test-unref.js +changelog.md diff --git a/client/nodejs/ledis/connection_breaker.js b/client/nodejs/ledis/connection_breaker.js new file mode 100644 index 0000000..489f5d5 --- /dev/null +++ b/client/nodejs/ledis/connection_breaker.js @@ -0,0 +1,80 @@ +var net = require('net'); + +var proxyPort = 6379; +var counter = 0; + +function breaker(conn) { + conn.end(); + conn.destroy(); +} + +var server = net.createServer(function(conn) { + counter++; + var proxyConn = net.createConnection({ + port: proxyPort + }); + conn.pipe(proxyConn); + proxyConn.pipe(conn); + proxyConn.on('end', function() { + conn.end(); + }); + conn.on('end', function() { + proxyConn.end(); + }); + conn.on('close', function() { + proxyConn.end(); + }); + proxyConn.on('close', function() { + conn.end(); + }); + proxyConn.on('error', function() { + conn.end(); + }); + conn.on('error', function() { + proxyConn.end(); + }); + + setTimeout(breaker.bind(null, conn), Math.floor(Math.random() * 2000)); +}); +server.listen(6479); + +var redis = require('./'); + +var port = 6479; + +var client = redis.createClient(6479, 'localhost'); + +function iter() { + var k = "k" + Math.floor(Math.random() * 10); + var coinflip = Math.random() > 0.5; + if (coinflip) { + client.set(k, k, function(err, resp) { + if (!err && resp !== "OK") { + console.log("Unexpected set response " + resp); + } + }); + } else { + client.get(k, function(err, resp) { + if (!err) { + if (k !== resp) { + console.log("Key response mismatch: " + k + " " + resp); + } + } + }); + } +} + +function iters() { + for (var i = 0; i < 100; ++i) { + iter(); + } + setTimeout(iters, 10); +} + +client.on("connect", function () { + iters(); +}); + +client.on("error", function (err) { + console.log("Client error " + err); +}); diff --git a/client/nodejs/ledis/index.js b/client/nodejs/ledis/index.js new file mode 100644 index 0000000..1d1a5c2 --- /dev/null +++ b/client/nodejs/ledis/index.js @@ -0,0 +1,1245 @@ +/*global Buffer require exports console setTimeout */ + +var net = require("net"), + util = require("./lib/util"), + Queue = require("./lib/queue"), + to_array = require("./lib/to_array"), + events = require("events"), + crypto = require("crypto"), + parsers = [], commands, + connection_id = 0, + //default_port = 6379, + default_port = 6380, //ledis' default port is 6380 --wenyekui + default_host = "127.0.0.1"; + +// can set this to true to enable for all connections +exports.debug_mode = false; + +var arraySlice = Array.prototype.slice +function trace() { + if (!exports.debug_mode) return; + console.log.apply(null, arraySlice.call(arguments)) +} + +// hiredis might not be installed +try { + require("./lib/parser/hiredis"); + parsers.push(require("./lib/parser/hiredis")); +} catch (err) { + if (exports.debug_mode) { + console.warn("hiredis parser not installed."); + } +} + +parsers.push(require("./lib/parser/javascript")); + +function RedisClient(stream, options) { + this.stream = stream; + this.options = options = options || {}; + + this.connection_id = ++connection_id; + this.connected = false; + this.ready = false; + this.connections = 0; + if (this.options.socket_nodelay === undefined) { + this.options.socket_nodelay = true; + } + if (this.options.socket_keepalive === undefined) { + this.options.socket_keepalive = true; + } + this.should_buffer = false; + this.command_queue_high_water = this.options.command_queue_high_water || 1000; + this.command_queue_low_water = this.options.command_queue_low_water || 0; + this.max_attempts = null; + if (options.max_attempts && !isNaN(options.max_attempts) && options.max_attempts > 0) { + this.max_attempts = +options.max_attempts; + } + this.command_queue = new Queue(); // holds sent commands to de-pipeline them + this.offline_queue = new Queue(); // holds commands issued but not able to be sent + this.commands_sent = 0; + this.connect_timeout = false; + if (options.connect_timeout && !isNaN(options.connect_timeout) && options.connect_timeout > 0) { + this.connect_timeout = +options.connect_timeout; + } + this.enable_offline_queue = true; + if (typeof this.options.enable_offline_queue === "boolean") { + this.enable_offline_queue = this.options.enable_offline_queue; + } + this.retry_max_delay = null; + if (options.retry_max_delay !== undefined && !isNaN(options.retry_max_delay) && options.retry_max_delay > 0) { + this.retry_max_delay = options.retry_max_delay; + } + + this.initialize_retry_vars(); + this.pub_sub_mode = false; + this.subscription_set = {}; + this.monitoring = false; + this.closing = false; + this.server_info = {}; + this.auth_pass = null; + if (options.auth_pass !== undefined) { + this.auth_pass = options.auth_pass; + } + this.parser_module = null; + this.selected_db = null; // save the selected db here, used when reconnecting + + this.old_state = null; + + this.install_stream_listeners(); + + events.EventEmitter.call(this); +} +util.inherits(RedisClient, events.EventEmitter); +//exports.RedisClient = RedisClient; +exports.LedisClient = RedisClient; //export RedisClient as LedisClient --wenyekui + +RedisClient.prototype.install_stream_listeners = function() { + var self = this; + + this.stream.on("connect", function () { + self.on_connect(); + }); + + this.stream.on("data", function (buffer_from_socket) { + self.on_data(buffer_from_socket); + }); + + this.stream.on("error", function (msg) { + self.on_error(msg.message); + }); + + this.stream.on("close", function () { + self.connection_gone("close"); + }); + + this.stream.on("end", function () { + self.connection_gone("end"); + }); + + this.stream.on("drain", function () { + self.should_buffer = false; + self.emit("drain"); + }); +}; + +RedisClient.prototype.initialize_retry_vars = function () { + this.retry_timer = null; + this.retry_totaltime = 0; + this.retry_delay = 150; + this.retry_backoff = 1.7; + this.attempts = 1; +}; + +RedisClient.prototype.unref = function () { + trace("User requesting to unref the connection"); + if (this.connected) { + trace("unref'ing the socket connection"); + this.stream.unref(); + } + else { + trace("Not connected yet, will unref later"); + this.once("connect", function () { + this.unref(); + }) + } +}; + +// flush offline_queue and command_queue, erroring any items with a callback first +RedisClient.prototype.flush_and_error = function (message) { + var command_obj, error; + + error = new Error(message); + + while (this.offline_queue.length > 0) { + command_obj = this.offline_queue.shift(); + if (typeof command_obj.callback === "function") { + try { + command_obj.callback(error); + } catch (callback_err) { + process.nextTick(function () { + throw callback_err; + }); + } + } + } + this.offline_queue = new Queue(); + + while (this.command_queue.length > 0) { + command_obj = this.command_queue.shift(); + if (typeof command_obj.callback === "function") { + try { + command_obj.callback(error); + } catch (callback_err) { + process.nextTick(function () { + throw callback_err; + }); + } + } + } + this.command_queue = new Queue(); +}; + +RedisClient.prototype.on_error = function (msg) { + var message = "Ledis connection to " + this.host + ":" + this.port + " failed - " + msg; + + if (this.closing) { + return; + } + + if (exports.debug_mode) { + console.warn(message); + } + + this.flush_and_error(message); + + this.connected = false; + this.ready = false; + + this.emit("error", new Error(message)); + // "error" events get turned into exceptions if they aren't listened for. If the user handled this error + // then we should try to reconnect. + this.connection_gone("error"); +}; + +RedisClient.prototype.do_auth = function () { + var self = this; + + if (exports.debug_mode) { + console.log("Sending auth to " + self.host + ":" + self.port + " id " + self.connection_id); + } + self.send_anyway = true; + self.send_command("auth", [this.auth_pass], function (err, res) { + if (err) { + if (err.toString().match("LOADING")) { + // if redis is still loading the db, it will not authenticate and everything else will fail + console.log("Ledis still loading, trying to authenticate later"); + setTimeout(function () { + self.do_auth(); + }, 2000); // TODO - magic number alert + return; + } else if (err.toString().match("no password is set")) { + console.log("Warning: Redis server does not require a password, but a password was supplied.") + err = null; + res = "OK"; + } else { + return self.emit("error", new Error("Auth error: " + err.message)); + } + } + if (res.toString() !== "OK") { + return self.emit("error", new Error("Auth failed: " + res.toString())); + } + if (exports.debug_mode) { + console.log("Auth succeeded " + self.host + ":" + self.port + " id " + self.connection_id); + } + if (self.auth_callback) { + self.auth_callback(err, res); + self.auth_callback = null; + } + + // now we are really connected + self.emit("connect"); + self.initialize_retry_vars(); + + //if (self.options.no_ready_check) { + // self.on_ready(); + //} else { + // self.ready_check(); + //} + + this.on_ready(); //ready_check uses `info` command, ledisDB doesn't have `info` command, so we directly `on_ready` without `ready_check` --wenyekui + }); + + self.send_anyway = false; +}; + +RedisClient.prototype.on_connect = function () { + if (exports.debug_mode) { + console.log("Stream connected " + this.host + ":" + this.port + " id " + this.connection_id); + } + + this.connected = true; + this.ready = false; + this.connections += 1; + this.command_queue = new Queue(); + this.emitted_end = false; + if (this.options.socket_nodelay) { + this.stream.setNoDelay(); + } + this.stream.setKeepAlive(this.options.socket_keepalive); + this.stream.setTimeout(0); + + this.init_parser(); + + if (this.auth_pass) { + this.do_auth(); + } else { + this.emit("connect"); + this.initialize_retry_vars(); + + //if (this.options.no_ready_check) { + // this.on_ready(); + //} else { + // this.ready_check(); + //} + this.on_ready(); //ready_check uses `info` command, ledisDB doesn't have `info` command, so we directly `on_ready` without `ready_check` --wenyekui + } +}; + +RedisClient.prototype.init_parser = function () { + var self = this; + + if (this.options.parser) { + if (! parsers.some(function (parser) { + if (parser.name === self.options.parser) { + self.parser_module = parser; + if (exports.debug_mode) { + console.log("Using parser module: " + self.parser_module.name); + } + return true; + } + })) { + throw new Error("Couldn't find named parser " + self.options.parser + " on this system"); + } + } else { + if (exports.debug_mode) { + console.log("Using default parser module: " + parsers[0].name); + } + this.parser_module = parsers[0]; + } + + this.parser_module.debug_mode = exports.debug_mode; + + // return_buffers sends back Buffers from parser to callback. detect_buffers sends back Buffers from parser, but + // converts to Strings if the input arguments are not Buffers. + this.reply_parser = new this.parser_module.Parser({ + return_buffers: self.options.return_buffers || self.options.detect_buffers || false + }); + + // "reply error" is an error sent back by Redis + this.reply_parser.on("reply error", function (reply) { + if (reply instanceof Error) { + self.return_error(reply); + } else { + self.return_error(new Error(reply)); + } + }); + this.reply_parser.on("reply", function (reply) { + self.return_reply(reply); + }); + // "error" is bad. Somehow the parser got confused. It'll try to reset and continue. + this.reply_parser.on("error", function (err) { + self.emit("error", new Error("Ledis reply parser error: " + err.stack)); + }); +}; + +RedisClient.prototype.on_ready = function () { + var self = this; + + this.ready = true; + + if (this.old_state !== null) { + this.monitoring = this.old_state.monitoring; + this.pub_sub_mode = this.old_state.pub_sub_mode; + this.selected_db = this.old_state.selected_db; + this.old_state = null; + } + + // magically restore any modal commands from a previous connection + if (this.selected_db !== null) { + // this trick works if and only if the following send_command + // never goes into the offline queue + var pub_sub_mode = this.pub_sub_mode; + this.pub_sub_mode = false; + this.send_command('select', [this.selected_db]); + this.pub_sub_mode = pub_sub_mode; + } + if (this.pub_sub_mode === true) { + // only emit "ready" when all subscriptions were made again + var callback_count = 0; + var callback = function () { + callback_count--; + if (callback_count === 0) { + self.emit("ready"); + } + }; + Object.keys(this.subscription_set).forEach(function (key) { + var parts = key.split(" "); + if (exports.debug_mode) { + console.warn("sending pub/sub on_ready " + parts[0] + ", " + parts[1]); + } + callback_count++; + self.send_command(parts[0] + "scribe", [parts[1]], callback); + }); + return; + } else if (this.monitoring) { + this.send_command("monitor"); + } else { + this.send_offline_queue(); + } + this.emit("ready"); +}; + +RedisClient.prototype.on_info_cmd = function (err, res) { + var self = this, obj = {}, lines, retry_time; + + if (err) { + return self.emit("error", new Error("Ready check failed: " + err.message)); + } + + lines = res.toString().split("\r\n"); + + lines.forEach(function (line) { + var parts = line.split(':'); + if (parts[1]) { + obj[parts[0]] = parts[1]; + } + }); + + obj.versions = []; + if( obj.redis_version ){ + obj.redis_version.split('.').forEach(function (num) { + obj.versions.push(+num); + }); + } + + // expose info key/vals to users + this.server_info = obj; + + if (!obj.loading || (obj.loading && obj.loading === "0")) { + if (exports.debug_mode) { + console.log("Ledis server ready."); + } + this.on_ready(); + } else { + retry_time = obj.loading_eta_seconds * 1000; + if (retry_time > 1000) { + retry_time = 1000; + } + if (exports.debug_mode) { + console.log("Ledis server still loading, trying again in " + retry_time); + } + setTimeout(function () { + self.ready_check(); + }, retry_time); + } +}; + +RedisClient.prototype.ready_check = function () { + var self = this; + + if (exports.debug_mode) { + console.log("checking server ready state..."); + } + + this.send_anyway = true; // secret flag to send_command to send something even if not "ready" + this.info(function (err, res) { + self.on_info_cmd(err, res); + }); + this.send_anyway = false; +}; + +RedisClient.prototype.send_offline_queue = function () { + var command_obj, buffered_writes = 0; + + while (this.offline_queue.length > 0) { + command_obj = this.offline_queue.shift(); + if (exports.debug_mode) { + console.log("Sending offline command: " + command_obj.command); + } + buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback); + } + this.offline_queue = new Queue(); + // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue + + if (!buffered_writes) { + this.should_buffer = false; + this.emit("drain"); + } +}; + +RedisClient.prototype.connection_gone = function (why) { + var self = this; + + // If a retry is already in progress, just let that happen + if (this.retry_timer) { + return; + } + + if (exports.debug_mode) { + console.warn("Ledis connection is gone from " + why + " event."); + } + this.connected = false; + this.ready = false; + + if (this.old_state === null) { + var state = { + monitoring: this.monitoring, + pub_sub_mode: this.pub_sub_mode, + selected_db: this.selected_db + }; + this.old_state = state; + this.monitoring = false; + this.pub_sub_mode = false; + this.selected_db = null; + } + + // since we are collapsing end and close, users don't expect to be called twice + if (! this.emitted_end) { + this.emit("end"); + this.emitted_end = true; + } + + this.flush_and_error("Ledis connection gone from " + why + " event."); + + // If this is a requested shutdown, then don't retry + if (this.closing) { + this.retry_timer = null; + if (exports.debug_mode) { + console.warn("connection ended from quit command, not retrying."); + } + return; + } + + var nextDelay = Math.floor(this.retry_delay * this.retry_backoff); + if (this.retry_max_delay !== null && nextDelay > this.retry_max_delay) { + this.retry_delay = this.retry_max_delay; + } else { + this.retry_delay = nextDelay; + } + + if (exports.debug_mode) { + console.log("Retry connection in " + this.retry_delay + " ms"); + } + + if (this.max_attempts && this.attempts >= this.max_attempts) { + this.retry_timer = null; + // TODO - some people need a "Ledis is Broken mode" for future commands that errors immediately, and others + // want the program to exit. Right now, we just log, which doesn't really help in either case. + console.error("node_redis: Couldn't get Redis connection after " + this.max_attempts + " attempts."); + return; + } + + this.attempts += 1; + this.emit("reconnecting", { + delay: self.retry_delay, + attempt: self.attempts + }); + this.retry_timer = setTimeout(function () { + if (exports.debug_mode) { + console.log("Retrying connection..."); + } + + self.retry_totaltime += self.retry_delay; + + if (self.connect_timeout && self.retry_totaltime >= self.connect_timeout) { + self.retry_timer = null; + // TODO - engage Redis is Broken mode for future commands, or whatever + console.error("node_redis: Couldn't get Redis connection after " + self.retry_totaltime + "ms."); + return; + } + + self.stream = net.createConnection(self.port, self.host); + self.install_stream_listeners(); + self.retry_timer = null; + }, this.retry_delay); +}; + +RedisClient.prototype.on_data = function (data) { + if (exports.debug_mode) { + console.log("net read " + this.host + ":" + this.port + " id " + this.connection_id + ": " + data.toString()); + } + + try { + this.reply_parser.execute(data); + } catch (err) { + // This is an unexpected parser problem, an exception that came from the parser code itself. + // Parser should emit "error" events if it notices things are out of whack. + // Callbacks that throw exceptions will land in return_reply(), below. + // TODO - it might be nice to have a different "error" event for different types of errors + this.emit("error", err); + } +}; + +RedisClient.prototype.return_error = function (err) { + var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength(); + + if (this.pub_sub_mode === false && queue_len === 0) { + this.command_queue = new Queue(); + this.emit("idle"); + } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; + } + + if (command_obj && typeof command_obj.callback === "function") { + try { + command_obj.callback(err); + } catch (callback_err) { + // if a callback throws an exception, re-throw it on a new stack so the parser can keep going + process.nextTick(function () { + throw callback_err; + }); + } + } else { + console.log("node_redis: no callback to send error: " + err.message); + // this will probably not make it anywhere useful, but we might as well throw + process.nextTick(function () { + throw err; + }); + } +}; + +// if a callback throws an exception, re-throw it on a new stack so the parser can keep going. +// if a domain is active, emit the error on the domain, which will serve the same function. +// put this try/catch in its own function because V8 doesn't optimize this well yet. +function try_callback(callback, reply) { + try { + callback(null, reply); + } catch (err) { + if (process.domain) { + var currDomain = process.domain; + currDomain.emit('error', err); + if (process.domain === currDomain) { + currDomain.exit(); + } + } else { + process.nextTick(function () { + throw err; + }); + } + } +} + +// hgetall converts its replies to an Object. If the reply is empty, null is returned. +function reply_to_object(reply) { + var obj = {}, j, jl, key, val; + + if (reply.length === 0) { + return null; + } + + for (j = 0, jl = reply.length; j < jl; j += 2) { + key = reply[j].toString('binary'); + val = reply[j + 1]; + obj[key] = val; + } + + return obj; +} + +function reply_to_strings(reply) { + var i; + + if (Buffer.isBuffer(reply)) { + return reply.toString(); + } + + if (Array.isArray(reply)) { + for (i = 0; i < reply.length; i++) { + if (reply[i] !== null && reply[i] !== undefined) { + reply[i] = reply[i].toString(); + } + } + return reply; + } + + return reply; +} + +RedisClient.prototype.return_reply = function (reply) { + var command_obj, len, type, timestamp, argindex, args, queue_len; + + // If the "reply" here is actually a message received asynchronously due to a + // pubsub subscription, don't pop the command queue as we'll only be consuming + // the head command prematurely. + if (Array.isArray(reply) && reply.length > 0 && reply[0]) { + type = reply[0].toString(); + } + + if (this.pub_sub_mode && (type == 'message' || type == 'pmessage')) { + trace("received pubsub message"); + } + else { + command_obj = this.command_queue.shift(); + } + + queue_len = this.command_queue.getLength(); + + if (this.pub_sub_mode === false && queue_len === 0) { + this.command_queue = new Queue(); // explicitly reclaim storage from old Queue + this.emit("idle"); + } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; + } + + if (command_obj && !command_obj.sub_command) { + if (typeof command_obj.callback === "function") { + if (this.options.detect_buffers && command_obj.buffer_args === false) { + // If detect_buffers option was specified, then the reply from the parser will be Buffers. + // If this command did not use Buffer arguments, then convert the reply to Strings here. + reply = reply_to_strings(reply); + } + + // TODO - confusing and error-prone that hgetall is special cased in two places + if (reply && 'hgetall' === command_obj.command.toLowerCase()) { + reply = reply_to_object(reply); + } + + try_callback(command_obj.callback, reply); + } else if (exports.debug_mode) { + console.log("no callback for reply: " + (reply && reply.toString && reply.toString())); + } + } else if (this.pub_sub_mode || (command_obj && command_obj.sub_command)) { + if (Array.isArray(reply)) { + type = reply[0].toString(); + + if (type === "message") { + this.emit("message", reply[1].toString(), reply[2]); // channel, message + } else if (type === "pmessage") { + this.emit("pmessage", reply[1].toString(), reply[2].toString(), reply[3]); // pattern, channel, message + } else if (type === "subscribe" || type === "unsubscribe" || type === "psubscribe" || type === "punsubscribe") { + if (reply[2] === 0) { + this.pub_sub_mode = false; + if (this.debug_mode) { + console.log("All subscriptions removed, exiting pub/sub mode"); + } + } else { + this.pub_sub_mode = true; + } + // subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback + // TODO - document this or fix it so it works in a more obvious way + // reply[1] can be null + var reply1String = (reply[1] === null) ? null : reply[1].toString(); + if (command_obj && typeof command_obj.callback === "function") { + try_callback(command_obj.callback, reply1String); + } + this.emit(type, reply1String, reply[2]); // channel, count + } else { + throw new Error("subscriptions are active but got unknown reply type " + type); + } + } else if (! this.closing) { + throw new Error("subscriptions are active but got an invalid reply: " + reply); + } + } else if (this.monitoring) { + len = reply.indexOf(" "); + timestamp = reply.slice(0, len); + argindex = reply.indexOf('"'); + args = reply.slice(argindex + 1, -1).split('" "').map(function (elem) { + return elem.replace(/\\"/g, '"'); + }); + this.emit("monitor", timestamp, args); + } else { + throw new Error("node_redis command queue state error. If you can reproduce this, please report it."); + } +}; + +// This Command constructor is ever so slightly faster than using an object literal, but more importantly, using +// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots. +function Command(command, args, sub_command, buffer_args, callback) { + this.command = command; + this.args = args; + this.sub_command = sub_command; + this.buffer_args = buffer_args; + this.callback = callback; +} + +RedisClient.prototype.send_command = function (command, args, callback) { + var arg, command_obj, i, il, elem_count, buffer_args, stream = this.stream, command_str = "", buffered_writes = 0, last_arg_type, lcaseCommand; + + if (typeof command !== "string") { + throw new Error("First argument to send_command must be the command name string, not " + typeof command); + } + + if (Array.isArray(args)) { + if (typeof callback === "function") { + // probably the fastest way: + // client.command([arg1, arg2], cb); (straight passthrough) + // send_command(command, [arg1, arg2], cb); + } else if (! callback) { + // most people find this variable argument length form more convenient, but it uses arguments, which is slower + // client.command(arg1, arg2, cb); (wraps up arguments into an array) + // send_command(command, [arg1, arg2, cb]); + // client.command(arg1, arg2); (callback is optional) + // send_command(command, [arg1, arg2]); + // client.command(arg1, arg2, undefined); (callback is undefined) + // send_command(command, [arg1, arg2, undefined]); + last_arg_type = typeof args[args.length - 1]; + if (last_arg_type === "function" || last_arg_type === "undefined") { + callback = args.pop(); + } + } else { + throw new Error("send_command: last argument must be a callback or undefined"); + } + } else { + throw new Error("send_command: second argument must be an array"); + } + + if (callback && process.domain) callback = process.domain.bind(callback); + + // if the last argument is an array and command is sadd or srem, expand it out: + // client.sadd(arg1, [arg2, arg3, arg4], cb); + // converts to: + // client.sadd(arg1, arg2, arg3, arg4, cb); + lcaseCommand = command.toLowerCase(); + if ((lcaseCommand === 'sadd' || lcaseCommand === 'srem') && args.length > 0 && Array.isArray(args[args.length - 1])) { + args = args.slice(0, -1).concat(args[args.length - 1]); + } + + // if the value is undefined or null and command is set or setx, need not to send message to redis + if (command === 'set' || command === 'setex') { + if(args[args.length - 1] === undefined || args[args.length - 1] === null) { + var err = new Error('send_command: ' + command + ' value must not be undefined or null'); + return callback && callback(err); + } + } + + buffer_args = false; + for (i = 0, il = args.length, arg; i < il; i += 1) { + if (Buffer.isBuffer(args[i])) { + buffer_args = true; + } + } + + command_obj = new Command(command, args, false, buffer_args, callback); + + if ((!this.ready && !this.send_anyway) || !stream.writable) { + if (exports.debug_mode) { + if (!stream.writable) { + console.log("send command: stream is not writeable."); + } + } + + if (this.enable_offline_queue) { + if (exports.debug_mode) { + console.log("Queueing " + command + " for next server connection."); + } + this.offline_queue.push(command_obj); + this.should_buffer = true; + } else { + var not_writeable_error = new Error('send_command: stream not writeable. enable_offline_queue is false'); + if (command_obj.callback) { + command_obj.callback(not_writeable_error); + } else { + throw not_writeable_error; + } + } + + return false; + } + + if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") { + this.pub_sub_command(command_obj); + } else if (command === "monitor") { + this.monitoring = true; + } else if (command === "quit") { + this.closing = true; + } else if (this.pub_sub_mode === true) { + throw new Error("Connection in subscriber mode, only subscriber commands may be used"); + } + this.command_queue.push(command_obj); + this.commands_sent += 1; + + elem_count = args.length + 1; + + // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg. + // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer. + + command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n"; + + if (! buffer_args) { // Build up a string and send entire command in one write + for (i = 0, il = args.length, arg; i < il; i += 1) { + arg = args[i]; + if (typeof arg !== "string") { + arg = String(arg); + } + command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"; + } + if (exports.debug_mode) { + console.log("send " + this.host + ":" + this.port + " id " + this.connection_id + ": " + command_str); + } + buffered_writes += !stream.write(command_str); + } else { + if (exports.debug_mode) { + console.log("send command (" + command_str + ") has Buffer arguments"); + } + buffered_writes += !stream.write(command_str); + + for (i = 0, il = args.length, arg; i < il; i += 1) { + arg = args[i]; + if (!(Buffer.isBuffer(arg) || arg instanceof String)) { + arg = String(arg); + } + + if (Buffer.isBuffer(arg)) { + if (arg.length === 0) { + if (exports.debug_mode) { + console.log("send_command: using empty string for 0 length buffer"); + } + buffered_writes += !stream.write("$0\r\n\r\n"); + } else { + buffered_writes += !stream.write("$" + arg.length + "\r\n"); + buffered_writes += !stream.write(arg); + buffered_writes += !stream.write("\r\n"); + if (exports.debug_mode) { + console.log("send_command: buffer send " + arg.length + " bytes"); + } + } + } else { + if (exports.debug_mode) { + console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg); + } + buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"); + } + } + } + if (exports.debug_mode) { + console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer); + } + if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) { + this.should_buffer = true; + } + return !this.should_buffer; +}; + +RedisClient.prototype.pub_sub_command = function (command_obj) { + var i, key, command, args; + + if (this.pub_sub_mode === false && exports.debug_mode) { + console.log("Entering pub/sub mode from " + command_obj.command); + } + this.pub_sub_mode = true; + command_obj.sub_command = true; + + command = command_obj.command; + args = command_obj.args; + if (command === "subscribe" || command === "psubscribe") { + if (command === "subscribe") { + key = "sub"; + } else { + key = "psub"; + } + for (i = 0; i < args.length; i++) { + this.subscription_set[key + " " + args[i]] = true; + } + } else { + if (command === "unsubscribe") { + key = "sub"; + } else { + key = "psub"; + } + for (i = 0; i < args.length; i++) { + delete this.subscription_set[key + " " + args[i]]; + } + } +}; + +RedisClient.prototype.end = function () { + this.stream._events = {}; + + //clear retry_timer + if(this.retry_timer){ + clearTimeout(this.retry_timer); + this.retry_timer=null; + } + this.stream.on("error", function(){}); + + this.connected = false; + this.ready = false; + this.closing = true; + return this.stream.destroySoon(); +}; + +function Multi(client, args) { + this._client = client; + this.queue = [["MULTI"]]; + if (Array.isArray(args)) { + this.queue = this.queue.concat(args); + } +} + +//exports.Multi = Multi; //do not export Multi, 'cause ledis does not support multi --wenyekui + +// take 2 arrays and return the union of their elements +function set_union(seta, setb) { + var obj = {}; + + seta.forEach(function (val) { + obj[val] = true; + }); + setb.forEach(function (val) { + obj[val] = true; + }); + return Object.keys(obj); +} + +// This static list of commands is updated from time to time. ./lib/commands.js can be updated with generate_commands.js +commands = require("./lib/commands"); + +commands.forEach(function (fullCommand) { + var command = fullCommand.split(' ')[0]; + + RedisClient.prototype[command] = function (args, callback) { + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command(command, args, callback); + } else { + return this.send_command(command, to_array(arguments)); + } + }; + RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command]; + + Multi.prototype[command] = function () { + this.queue.push([command].concat(to_array(arguments))); + return this; + }; + Multi.prototype[command.toUpperCase()] = Multi.prototype[command]; +}); + +// store db in this.select_db to restore it on reconnect +RedisClient.prototype.select = function (db, callback) { + var self = this; + + this.send_command('select', [db], function (err, res) { + if (err === null) { + self.selected_db = db; + } + if (typeof(callback) === 'function') { + callback(err, res); + } else if (err) { + self.emit('error', err); + } + }); +}; +RedisClient.prototype.SELECT = RedisClient.prototype.select; + +// Stash auth for connect and reconnect. Send immediately if already connected. +RedisClient.prototype.auth = function () { + var args = to_array(arguments); + this.auth_pass = args[0]; + this.auth_callback = args[1]; + if (exports.debug_mode) { + console.log("Saving auth as " + this.auth_pass); + } + + if (this.connected) { + this.send_command("auth", args); + } +}; +RedisClient.prototype.AUTH = RedisClient.prototype.auth; + +RedisClient.prototype.hmget = function (arg1, arg2, arg3) { + if (Array.isArray(arg2) && typeof arg3 === "function") { + return this.send_command("hmget", [arg1].concat(arg2), arg3); + } else if (Array.isArray(arg1) && typeof arg2 === "function") { + return this.send_command("hmget", arg1, arg2); + } else { + return this.send_command("hmget", to_array(arguments)); + } +}; +RedisClient.prototype.HMGET = RedisClient.prototype.hmget; + +RedisClient.prototype.hmset = function (args, callback) { + var tmp_args, tmp_keys, i, il, key; + + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command("hmset", args, callback); + } + + args = to_array(arguments); + if (typeof args[args.length - 1] === "function") { + callback = args[args.length - 1]; + args.length -= 1; + } else { + callback = null; + } + + if (args.length === 2 && (typeof args[0] === "string" || typeof args[0] === "number") && typeof args[1] === "object") { + // User does: client.hmset(key, {key1: val1, key2: val2}) + // assuming key is a string, i.e. email address + + // if key is a number, i.e. timestamp, convert to string + if (typeof args[0] === "number") { + args[0] = args[0].toString(); + } + + tmp_args = [ args[0] ]; + tmp_keys = Object.keys(args[1]); + for (i = 0, il = tmp_keys.length; i < il ; i++) { + key = tmp_keys[i]; + tmp_args.push(key); + tmp_args.push(args[1][key]); + } + args = tmp_args; + } + + return this.send_command("hmset", args, callback); +}; +RedisClient.prototype.HMSET = RedisClient.prototype.hmset; + +Multi.prototype.hmset = function () { + var args = to_array(arguments), tmp_args; + if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") { + tmp_args = [ "hmset", args[0] ]; + Object.keys(args[1]).map(function (key) { + tmp_args.push(key); + tmp_args.push(args[1][key]); + }); + if (args[2]) { + tmp_args.push(args[2]); + } + args = tmp_args; + } else { + args.unshift("hmset"); + } + + this.queue.push(args); + return this; +}; +Multi.prototype.HMSET = Multi.prototype.hmset; + +Multi.prototype.exec = function (callback) { + var self = this; + var errors = []; + // drain queue, callback will catch "QUEUED" or error + // TODO - get rid of all of these anonymous functions which are elegant but slow + this.queue.forEach(function (args, index) { + var command = args[0], obj; + if (typeof args[args.length - 1] === "function") { + args = args.slice(1, -1); + } else { + args = args.slice(1); + } + if (args.length === 1 && Array.isArray(args[0])) { + args = args[0]; + } + if (command.toLowerCase() === 'hmset' && typeof args[1] === 'object') { + obj = args.pop(); + Object.keys(obj).forEach(function (key) { + args.push(key); + args.push(obj[key]); + }); + } + this._client.send_command(command, args, function (err, reply) { + if (err) { + var cur = self.queue[index]; + if (typeof cur[cur.length - 1] === "function") { + cur[cur.length - 1](err); + } else { + errors.push(new Error(err)); + } + } + }); + }, this); + + // TODO - make this callback part of Multi.prototype instead of creating it each time + return this._client.send_command("EXEC", [], function (err, replies) { + if (err) { + if (callback) { + errors.push(new Error(err)); + callback(errors); + return; + } else { + throw new Error(err); + } + } + + var i, il, reply, args; + + if (replies) { + for (i = 1, il = self.queue.length; i < il; i += 1) { + reply = replies[i - 1]; + args = self.queue[i]; + + // TODO - confusing and error-prone that hgetall is special cased in two places + if (reply && args[0].toLowerCase() === "hgetall") { + replies[i - 1] = reply = reply_to_object(reply); + } + + if (typeof args[args.length - 1] === "function") { + args[args.length - 1](null, reply); + } + } + } + + if (callback) { + callback(null, replies); + } + }); +}; +Multi.prototype.EXEC = Multi.prototype.exec; + +RedisClient.prototype.multi = function (args) { + return new Multi(this, args); +}; +RedisClient.prototype.MULTI = function (args) { + return new Multi(this, args); +}; + + +// stash original eval method +var eval_orig = RedisClient.prototype.eval; +// hook eval with an attempt to evalsha for cached scripts +RedisClient.prototype.eval = RedisClient.prototype.EVAL = function () { + var self = this, + args = to_array(arguments), + callback; + + if (typeof args[args.length - 1] === "function") { + callback = args.pop(); + } + + if (Array.isArray(args[0])) { + args = args[0]; + } + + // replace script source with sha value + var source = args[0]; + args[0] = crypto.createHash("sha1").update(source).digest("hex"); + + self.evalsha(args, function (err, reply) { + if (err && /NOSCRIPT/.test(err.message)) { + args[0] = source; + eval_orig.call(self, args, callback); + + } else if (callback) { + callback(err, reply); + } + }); +}; + + +exports.createClient = function (port_arg, host_arg, options) { + + var cnxFamily; + + if (options && options.family) { + cnxFamily = (options.family == 'IPv6' ? 6 : 4); + } + + var cnxOptions = { + 'port' : port_arg || default_port, + 'host' : host_arg || default_host, + 'family' : cnxFamily || '4' + }; + + var redis_client, net_client; + + net_client = net.createConnection(cnxOptions); + + redis_client = new RedisClient(net_client, options); + + redis_client.port = cnxOptions.port; + redis_client.host = cnxOptions.host; + + return redis_client; +}; + +exports.print = function (err, reply) { + if (err) { + console.log("Error: " + err); + } else { + console.log("Reply: " + reply); + } +}; diff --git a/client/nodejs/ledis/lib/commands.js b/client/nodejs/ledis/lib/commands.js new file mode 100644 index 0000000..2148669 --- /dev/null +++ b/client/nodejs/ledis/lib/commands.js @@ -0,0 +1,99 @@ +// This file was generated by ./generate_commands.js on Wed Apr 23 2014 14:51:21 GMT-0700 (PDT) +module.exports = [ + "quit", + + "bget", + "bdelete", + "bsetbit", + "bgetbit", + "bmsetbit", + "bcount", + "bopt", + "bexpire", + "bexpireat", + "bttl", + "bpersist", + + + "hdel", + "hexists", + "hget", + "hgetall", + "hincrby", + "hkeys", + "hlen", + "hmget", + "hmset", + "hset", + "hvals", + + + "hclear", + "hmclear", + "hexpire", + "hexpireat", + "httl", + "hpersist", + + + "decr", + "decrby", + "del", + "exists", + "get", + "getset", + "incr", + "incrby", + "mget", + "mset", + "set", + "setnx", + "expire", + "expireat", + "ttl", + "persist", + + "lindex", + "llen", + "lpop", + "lrange", + "lpush", + "rpop", + "rpush", + + + "lclear", + "lmclear", + "lexpire", + "lexpireat", + "lttl", + "lpersist", + + "slaveof", + "fullsync", + "sync", + + + "zadd", + "zcard", + "zcount", + "zincrby", + "zrange", + "zrangebyscore", + "zrank", + "zrem", + "zremrangebyrank", + "zremrangebyscore", + "zrevrange", + "zrevrank", + "zrevrangebyscore", + "zscore", + + + "zclear", + "zmclear", + "zexpire", + "zexpireat", + "zttl", + "zpersist", +]; diff --git a/client/nodejs/ledis/lib/parser/hiredis.js b/client/nodejs/ledis/lib/parser/hiredis.js new file mode 100644 index 0000000..940bfee --- /dev/null +++ b/client/nodejs/ledis/lib/parser/hiredis.js @@ -0,0 +1,46 @@ +var events = require("events"), + util = require("../util"), + hiredis = require("hiredis"); + +exports.debug_mode = false; +exports.name = "hiredis"; + +function HiredisReplyParser(options) { + this.name = exports.name; + this.options = options || {}; + this.reset(); + events.EventEmitter.call(this); +} + +util.inherits(HiredisReplyParser, events.EventEmitter); + +exports.Parser = HiredisReplyParser; + +HiredisReplyParser.prototype.reset = function () { + this.reader = new hiredis.Reader({ + return_buffers: this.options.return_buffers || false + }); +}; + +HiredisReplyParser.prototype.execute = function (data) { + var reply; + this.reader.feed(data); + while (true) { + try { + reply = this.reader.get(); + } catch (err) { + this.emit("error", err); + break; + } + + if (reply === undefined) { + break; + } + + if (reply && reply.constructor === Error) { + this.emit("reply error", reply); + } else { + this.emit("reply", reply); + } + } +}; diff --git a/client/nodejs/ledis/lib/parser/javascript.js b/client/nodejs/ledis/lib/parser/javascript.js new file mode 100644 index 0000000..0990cc0 --- /dev/null +++ b/client/nodejs/ledis/lib/parser/javascript.js @@ -0,0 +1,301 @@ +var events = require("events"), + util = require("../util"); + +function Packet(type, size) { + this.type = type; + this.size = +size; +} + +exports.name = "javascript"; +exports.debug_mode = false; + +function ReplyParser(options) { + this.name = exports.name; + this.options = options || { }; + + this._buffer = null; + this._offset = 0; + this._encoding = "utf-8"; + this._debug_mode = options.debug_mode; + this._reply_type = null; +} + +util.inherits(ReplyParser, events.EventEmitter); + +exports.Parser = ReplyParser; + +function IncompleteReadBuffer(message) { + this.name = "IncompleteReadBuffer"; + this.message = message; +} +util.inherits(IncompleteReadBuffer, Error); + +// Buffer.toString() is quite slow for small strings +function small_toString(buf, start, end) { + var tmp = "", i; + + for (i = start; i < end; i++) { + tmp += String.fromCharCode(buf[i]); + } + + return tmp; +} + +ReplyParser.prototype._parseResult = function (type) { + var start, end, offset, packetHeader; + + if (type === 43 || type === 45) { // + or - + // up to the delimiter + end = this._packetEndOffset() - 1; + start = this._offset; + + // include the delimiter + this._offset = end + 2; + + if (end > this._buffer.length) { + this._offset = start; + throw new IncompleteReadBuffer("Wait for more data."); + } + + if (this.options.return_buffers) { + return this._buffer.slice(start, end); + } else { + if (end - start < 65536) { // completely arbitrary + return small_toString(this._buffer, start, end); + } else { + return this._buffer.toString(this._encoding, start, end); + } + } + } else if (type === 58) { // : + // up to the delimiter + end = this._packetEndOffset() - 1; + start = this._offset; + + // include the delimiter + this._offset = end + 2; + + if (end > this._buffer.length) { + this._offset = start; + throw new IncompleteReadBuffer("Wait for more data."); + } + + if (this.options.return_buffers) { + return this._buffer.slice(start, end); + } + + // return the coerced numeric value + return +small_toString(this._buffer, start, end); + } else if (type === 36) { // $ + // set a rewind point, as the packet could be larger than the + // buffer in memory + offset = this._offset - 1; + + packetHeader = new Packet(type, this.parseHeader()); + + // packets with a size of -1 are considered null + if (packetHeader.size === -1) { + return undefined; + } + + end = this._offset + packetHeader.size; + start = this._offset; + + // set the offset to after the delimiter + this._offset = end + 2; + + if (end > this._buffer.length) { + this._offset = offset; + throw new IncompleteReadBuffer("Wait for more data."); + } + + if (this.options.return_buffers) { + return this._buffer.slice(start, end); + } else { + return this._buffer.toString(this._encoding, start, end); + } + } else if (type === 42) { // * + offset = this._offset; + packetHeader = new Packet(type, this.parseHeader()); + + if (packetHeader.size < 0) { + return null; + } + + if (packetHeader.size > this._bytesRemaining()) { + this._offset = offset - 1; + throw new IncompleteReadBuffer("Wait for more data."); + } + + var reply = [ ]; + var ntype, i, res; + + offset = this._offset - 1; + + for (i = 0; i < packetHeader.size; i++) { + ntype = this._buffer[this._offset++]; + + if (this._offset > this._buffer.length) { + throw new IncompleteReadBuffer("Wait for more data."); + } + res = this._parseResult(ntype); + if (res === undefined) { + res = null; + } + reply.push(res); + } + + return reply; + } +}; + +ReplyParser.prototype.execute = function (buffer) { + this.append(buffer); + + var type, ret, offset; + + while (true) { + offset = this._offset; + try { + // at least 4 bytes: :1\r\n + if (this._bytesRemaining() < 4) { + break; + } + + type = this._buffer[this._offset++]; + + if (type === 43) { // + + ret = this._parseResult(type); + + if (ret === null) { + break; + } + + this.send_reply(ret); + } else if (type === 45) { // - + ret = this._parseResult(type); + + if (ret === null) { + break; + } + + this.send_error(ret); + } else if (type === 58) { // : + ret = this._parseResult(type); + + if (ret === null) { + break; + } + + this.send_reply(ret); + } else if (type === 36) { // $ + ret = this._parseResult(type); + + if (ret === null) { + break; + } + + // check the state for what is the result of + // a -1, set it back up for a null reply + if (ret === undefined) { + ret = null; + } + + this.send_reply(ret); + } else if (type === 42) { // * + // set a rewind point. if a failure occurs, + // wait for the next execute()/append() and try again + offset = this._offset - 1; + + ret = this._parseResult(type); + + this.send_reply(ret); + } + } catch (err) { + // catch the error (not enough data), rewind, and wait + // for the next packet to appear + if (! (err instanceof IncompleteReadBuffer)) { + throw err; + } + this._offset = offset; + break; + } + } +}; + +ReplyParser.prototype.append = function (newBuffer) { + if (!newBuffer) { + return; + } + + // first run + if (this._buffer === null) { + this._buffer = newBuffer; + + return; + } + + // out of data + if (this._offset >= this._buffer.length) { + this._buffer = newBuffer; + this._offset = 0; + + return; + } + + // very large packet + // check for concat, if we have it, use it + if (Buffer.concat !== undefined) { + this._buffer = Buffer.concat([this._buffer.slice(this._offset), newBuffer]); + } else { + var remaining = this._bytesRemaining(), + newLength = remaining + newBuffer.length, + tmpBuffer = new Buffer(newLength); + + this._buffer.copy(tmpBuffer, 0, this._offset); + newBuffer.copy(tmpBuffer, remaining, 0); + + this._buffer = tmpBuffer; + } + + this._offset = 0; +}; + +ReplyParser.prototype.parseHeader = function () { + var end = this._packetEndOffset(), + value = small_toString(this._buffer, this._offset, end - 1); + + this._offset = end + 1; + + return value; +}; + +ReplyParser.prototype._packetEndOffset = function () { + var offset = this._offset; + + while (this._buffer[offset] !== 0x0d && this._buffer[offset + 1] !== 0x0a) { + offset++; + + if (offset >= this._buffer.length) { + throw new IncompleteReadBuffer("didn't see LF after NL reading multi bulk count (" + offset + " => " + this._buffer.length + ", " + this._offset + ")"); + } + } + + offset++; + return offset; +}; + +ReplyParser.prototype._bytesRemaining = function () { + return (this._buffer.length - this._offset) < 0 ? 0 : (this._buffer.length - this._offset); +}; + +ReplyParser.prototype.parser_error = function (message) { + this.emit("error", message); +}; + +ReplyParser.prototype.send_error = function (reply) { + this.emit("reply error", reply); +}; + +ReplyParser.prototype.send_reply = function (reply) { + this.emit("reply", reply); +}; diff --git a/client/nodejs/ledis/lib/queue.js b/client/nodejs/ledis/lib/queue.js new file mode 100644 index 0000000..3fc87ab --- /dev/null +++ b/client/nodejs/ledis/lib/queue.js @@ -0,0 +1,59 @@ +// Queue class adapted from Tim Caswell's pattern library +// http://github.com/creationix/pattern/blob/master/lib/pattern/queue.js + +function Queue() { + this.tail = []; + this.head = []; + this.offset = 0; +} + +Queue.prototype.shift = function () { + if (this.offset === this.head.length) { + var tmp = this.head; + tmp.length = 0; + this.head = this.tail; + this.tail = tmp; + this.offset = 0; + if (this.head.length === 0) { + return; + } + } + return this.head[this.offset++]; // sorry, JSLint +}; + +Queue.prototype.push = function (item) { + return this.tail.push(item); +}; + +Queue.prototype.forEach = function (fn, thisv) { + var array = this.head.slice(this.offset), i, il; + + array.push.apply(array, this.tail); + + if (thisv) { + for (i = 0, il = array.length; i < il; i += 1) { + fn.call(thisv, array[i], i, array); + } + } else { + for (i = 0, il = array.length; i < il; i += 1) { + fn(array[i], i, array); + } + } + + return array; +}; + +Queue.prototype.getLength = function () { + return this.head.length - this.offset + this.tail.length; +}; + +Object.defineProperty(Queue.prototype, "length", { + get: function () { + return this.getLength(); + } +}); + + +if (typeof module !== "undefined" && module.exports) { + module.exports = Queue; +} diff --git a/client/nodejs/ledis/lib/to_array.js b/client/nodejs/ledis/lib/to_array.js new file mode 100644 index 0000000..88a57e1 --- /dev/null +++ b/client/nodejs/ledis/lib/to_array.js @@ -0,0 +1,12 @@ +function to_array(args) { + var len = args.length, + arr = new Array(len), i; + + for (i = 0; i < len; i += 1) { + arr[i] = args[i]; + } + + return arr; +} + +module.exports = to_array; diff --git a/client/nodejs/ledis/lib/util.js b/client/nodejs/ledis/lib/util.js new file mode 100644 index 0000000..fc255ae --- /dev/null +++ b/client/nodejs/ledis/lib/util.js @@ -0,0 +1,11 @@ +// Support for very old versions of node where the module was called "sys". At some point, we should abandon this. + +var util; + +try { + util = require("util"); +} catch (err) { + util = require("sys"); +} + +module.exports = util; diff --git a/client/nodejs/ledis/package.json b/client/nodejs/ledis/package.json new file mode 100644 index 0000000..19f30e3 --- /dev/null +++ b/client/nodejs/ledis/package.json @@ -0,0 +1,15 @@ +{ + "name": "ledis", + "version": "0.1", + "description": "Ledis client library", + "keywords": [ + "ledis", + "nosql" + ], + "main": "./index.js", + "devDependencies": { + "metrics": ">=0.1.5", + "colors": "~0.6.0-1", + "underscore": "~1.4.4" + } +} From 79eb943bbc1a2b821001c1a6f7564975d8d4efe0 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 5 Aug 2014 11:41:20 +0800 Subject: [PATCH 48/51] add LICENSE --- client/nodejs/LICENSE | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 client/nodejs/LICENSE diff --git a/client/nodejs/LICENSE b/client/nodejs/LICENSE new file mode 100644 index 0000000..c15442b --- /dev/null +++ b/client/nodejs/LICENSE @@ -0,0 +1,8 @@ +Copyright (c) 2010 Matthew Ranney, http://ranney.com/ + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + From 5d32c0b5b8602715caa8879ac9bc06a8352bd227 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 5 Aug 2014 11:48:28 +0800 Subject: [PATCH 49/51] Update LICENSE --- client/nodejs/LICENSE | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/client/nodejs/LICENSE b/client/nodejs/LICENSE index c15442b..039d121 100644 --- a/client/nodejs/LICENSE +++ b/client/nodejs/LICENSE @@ -1,8 +1,18 @@ +The MIT License (MIT) + Copyright (c) 2010 Matthew Ranney, http://ranney.com/ -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions +of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 5926ba2b4d3ac342f2425085bbf64d089c8b2a50 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 5 Aug 2014 11:54:28 +0800 Subject: [PATCH 50/51] remove the repl cmds --- client/nodejs/ledis/lib/commands.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/nodejs/ledis/lib/commands.js b/client/nodejs/ledis/lib/commands.js index 2148669..8e5a524 100644 --- a/client/nodejs/ledis/lib/commands.js +++ b/client/nodejs/ledis/lib/commands.js @@ -69,11 +69,6 @@ module.exports = [ "lttl", "lpersist", - "slaveof", - "fullsync", - "sync", - - "zadd", "zcard", "zcount", From 88edfa89f57f00e2746cd9c17b38e1a389f0e6c8 Mon Sep 17 00:00:00 2001 From: wenyekui Date: Tue, 5 Aug 2014 12:43:50 +0800 Subject: [PATCH 51/51] Update README.md --- client/nodejs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/nodejs/README.md b/client/nodejs/README.md index 4fa6560..7ababc5 100644 --- a/client/nodejs/README.md +++ b/client/nodejs/README.md @@ -4,7 +4,7 @@ This is a modification of [simplegeo/nodejs-redis](https://github.com/simplegeo/ ###Setup Just copy (or move) the ledis directory into your project's **node_modules** directory. - cp -r /path/to/github.com/src/ledisdb/client/nodejs/ledis /path/to/your/node_modules/ + cp -r /path/to/ledisdb/client/nodejs/ledis /path/to/your/node_modules/ ###Example Below is the total content of example.js, including the ledisDB's special commands.