From 55bce5f553786aa958378f9b7340884133a4de97 Mon Sep 17 00:00:00 2001 From: siddontang Date: Sat, 21 Jun 2014 22:12:37 +0800 Subject: [PATCH 1/4] add client ledis lib and ledis-cli --- client/go/ledis-cli/main.go | 81 ++++++++ client/go/ledis/client.go | 96 ++++++++++ client/go/ledis/conn.go | 309 +++++++++++++++++++++++++++++++ client/go/ledis/garyburd_license | 13 ++ client/go/ledis/ledis_test.go | 15 ++ client/go/ledis/reply.go | 257 +++++++++++++++++++++++++ 6 files changed, 771 insertions(+) create mode 100644 client/go/ledis-cli/main.go create mode 100644 client/go/ledis/client.go create mode 100644 client/go/ledis/conn.go create mode 100644 client/go/ledis/garyburd_license create mode 100644 client/go/ledis/ledis_test.go create mode 100644 client/go/ledis/reply.go diff --git a/client/go/ledis-cli/main.go b/client/go/ledis-cli/main.go new file mode 100644 index 0000000..5cb94df --- /dev/null +++ b/client/go/ledis-cli/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "github.com/siddontang/ledisdb/client/go/ledis" + "os" + "strings" +) + +var ip = flag.String("h", "127.0.0.1", "ledisdb server ip (default 127.0.0.1)") +var port = flag.Int("p", 6380, "ledisdb server port (default 6380)") +var socket = flag.String("s", "", "ledisdb server socket, overwrite ip and port") + +func main() { + flag.Parse() + + cfg := new(ledis.Config) + if len(*socket) > 0 { + cfg.Addr = *socket + } else { + cfg.Addr = fmt.Sprintf("%s:%d", *ip, *port) + } + + cfg.MaxIdleConns = 1 + + c := ledis.NewClient(cfg) + + reader := bufio.NewReader(os.Stdin) + + for { + fmt.Printf("ledis %s > ", cfg.Addr) + + cmd, _ := reader.ReadString('\n') + + cmds := strings.Fields(cmd) + if len(cmds) == 0 { + continue + } else { + args := make([]interface{}, len(cmds[1:])) + for i := range args { + args[i] = cmds[1+i] + } + r, err := c.Do(cmds[0], args...) + if err != nil { + fmt.Printf("%s", err.Error()) + } else { + printReply(r) + } + + fmt.Printf("\n") + } + } +} + +func printReply(reply interface{}) { + switch reply := reply.(type) { + case int64: + fmt.Printf("(integer) %d", reply) + case string: + fmt.Printf("%q", reply) + case []byte: + fmt.Printf("%q", reply) + case nil: + fmt.Printf("(empty list or set)") + case ledis.Error: + fmt.Printf("%s", string(reply)) + case []interface{}: + for i, v := range reply { + fmt.Printf("%d) ", i) + if v == nil { + fmt.Printf("(nil)") + } else { + fmt.Printf("%q", v) + } + } + default: + fmt.Printf("invalid ledis reply") + } +} diff --git a/client/go/ledis/client.go b/client/go/ledis/client.go new file mode 100644 index 0000000..dfe35b8 --- /dev/null +++ b/client/go/ledis/client.go @@ -0,0 +1,96 @@ +package ledis + +import ( + "container/list" + "strings" + "sync" + "time" +) + +const ( + pingPeriod time.Duration = 3 * time.Second +) + +type Config struct { + Addr string + MaxIdleConns int +} + +type Client struct { + sync.Mutex + + cfg *Config + proto string + + conns *list.List +} + +func NewClient(cfg *Config) *Client { + c := new(Client) + + c.cfg = cfg + + if strings.Contains(cfg.Addr, "/") { + c.proto = "unix" + } else { + c.proto = "tcp" + } + + c.conns = list.New() + + return c +} + +func (c *Client) Do(cmd string, args ...interface{}) (interface{}, error) { + co := c.get() + r, err := co.Do(cmd, args...) + c.put(co) + + return r, err +} + +func (c *Client) Close() { + c.Lock() + defer c.Unlock() + + for c.conns.Len() > 0 { + e := c.conns.Front() + co := e.Value.(*Conn) + c.conns.Remove(e) + + co.finalize() + } +} + +func (c *Client) Get() *Conn { + return c.get() +} + +func (c *Client) get() *Conn { + c.Lock() + if c.conns.Len() == 0 { + c.Unlock() + + return c.newConn() + } else { + e := c.conns.Front() + co := e.Value.(*Conn) + c.conns.Remove(e) + + c.Unlock() + + return co + } +} + +func (c *Client) put(conn *Conn) { + c.Lock() + if c.conns.Len() >= c.cfg.MaxIdleConns { + c.Unlock() + conn.finalize() + } else { + conn.lastActive = time.Now() + c.conns.PushFront(conn) + c.Unlock() + } +} diff --git a/client/go/ledis/conn.go b/client/go/ledis/conn.go new file mode 100644 index 0000000..688460e --- /dev/null +++ b/client/go/ledis/conn.go @@ -0,0 +1,309 @@ +package ledis + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "net" + "strconv" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +type Conn struct { + client *Client + + c net.Conn + br *bufio.Reader + bw *bufio.Writer + + lastActive time.Time + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +func (c *Conn) Close() { + c.client.put(c) +} + +func (c *Conn) Do(cmd string, args ...interface{}) (interface{}, error) { + if err := c.connect(); err != nil { + return nil, err + } + + if err := c.writeCommand(cmd, args); err != nil { + c.finalize() + return nil, err + } + + if err := c.bw.Flush(); err != nil { + c.finalize() + return nil, err + } + + if reply, err := c.readReply(); err != nil { + c.finalize() + return nil, err + } else { + if e, ok := reply.(Error); ok { + return reply, e + } else { + return reply, nil + } + } +} + +func (c *Conn) finalize() { + if c.c != nil { + c.c.Close() + c.c = nil + } +} + +func (c *Conn) connect() error { + if c.c != nil { + return nil + } + + var err error + c.c, err = net.Dial(c.client.proto, c.client.cfg.Addr) + if err != nil { + return err + } + + if c.br != nil { + c.br.Reset(c.c) + } else { + c.br = bufio.NewReader(c.c) + } + + if c.bw != nil { + c.bw.Reset(c.c) + } else { + c.bw = bufio.NewWriter(c.c) + } + + return nil +} + +func (c *Conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *Conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *Conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *Conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *Conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *Conn) writeCommand(cmd string, args []interface{}) (err error) { + c.writeLen('*', 1+len(args)) + err = c.writeString(cmd) + for _, arg := range args { + if err != nil { + break + } + switch arg := arg.(type) { + case string: + err = c.writeString(arg) + case []byte: + err = c.writeBytes(arg) + case int: + err = c.writeInt64(int64(arg)) + case int64: + err = c.writeInt64(arg) + case float64: + err = c.writeFloat64(arg) + case bool: + if arg { + err = c.writeString("1") + } else { + err = c.writeString("0") + } + case nil: + err = c.writeString("") + default: + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + err = c.writeBytes(buf.Bytes()) + } + } + return err +} + +func (c *Conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, errors.New("ledis: long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, errors.New("ledis: bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, errors.New("ledis: malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, errors.New("ledis: illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, errors.New("ledis: malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, errors.New("ledis: malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, errors.New("ledis: illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *Conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, errors.New("ledis: short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, errors.New("ledis: bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, errors.New("ledis: unexpected response line") +} + +func (c *Client) newConn() *Conn { + co := new(Conn) + co.client = c + + return co +} diff --git a/client/go/ledis/garyburd_license b/client/go/ledis/garyburd_license new file mode 100644 index 0000000..8867881 --- /dev/null +++ b/client/go/ledis/garyburd_license @@ -0,0 +1,13 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. \ No newline at end of file diff --git a/client/go/ledis/ledis_test.go b/client/go/ledis/ledis_test.go new file mode 100644 index 0000000..6582d04 --- /dev/null +++ b/client/go/ledis/ledis_test.go @@ -0,0 +1,15 @@ +package ledis + +import ( + "testing" +) + +func TestClient(t *testing.T) { + cfg := new(Config) + cfg.Addr = "127.0.0.1:6380" + cfg.MaxIdleConns = 4 + + c := NewClient(cfg) + + c.Close() +} diff --git a/client/go/ledis/reply.go b/client/go/ledis/reply.go new file mode 100644 index 0000000..e9e8993 --- /dev/null +++ b/client/go/ledis/reply.go @@ -0,0 +1,257 @@ +package ledis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("ledis: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("ledis: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("ledis: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("ledis: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("ledis: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("ledis: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("ledis: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("ledis: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("ledis: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is deprecated. Use Values. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("ledis: unexpected type for Values, got type %T", reply) +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. If one of the array +// items is not a bulk string or nil, then Strings returns an error. +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + p, ok := reply[i].([]byte) + if !ok { + return nil, fmt.Errorf("ledis: unexpected element type for Strings, got type %T", reply[i]) + } + result[i] = string(p) + } + return result, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("ledis: unexpected type for Strings, got type %T", reply) +} From ba7ba1f137541440855e8b2902bdabcf11c9cdb7 Mon Sep 17 00:00:00 2001 From: siddontang Date: Sun, 22 Jun 2014 10:21:53 +0800 Subject: [PATCH 2/4] update ledis-cli output --- client/go/ledis-cli/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/go/ledis-cli/main.go b/client/go/ledis-cli/main.go index 5cb94df..2be69bc 100644 --- a/client/go/ledis-cli/main.go +++ b/client/go/ledis-cli/main.go @@ -68,12 +68,15 @@ func printReply(reply interface{}) { fmt.Printf("%s", string(reply)) case []interface{}: for i, v := range reply { - fmt.Printf("%d) ", i) + fmt.Printf("%d) ", i+1) if v == nil { fmt.Printf("(nil)") } else { fmt.Printf("%q", v) } + if i != len(reply)-1 { + fmt.Printf("\n") + } } default: fmt.Printf("invalid ledis reply") From 594f6a5d0df8ad54ec926fe0352aac463144ee1a Mon Sep 17 00:00:00 2001 From: siddontang Date: Sun, 22 Jun 2014 10:39:23 +0800 Subject: [PATCH 3/4] use ledis client --- client/go/redis/commandinfo.go | 45 --- client/go/redis/conn.go | 418 --------------------------- client/go/redis/doc.go | 167 ----------- client/go/redis/log.go | 117 -------- client/go/redis/pool.go | 358 ----------------------- client/go/redis/pubsub.go | 129 --------- client/go/redis/redis.go | 44 --- client/go/redis/reply.go | 271 ----------------- client/go/redis/scan.go | 513 --------------------------------- client/go/redis/script.go | 86 ------ cmd/ledis-benchmark/main.go | 18 +- server/app_test.go | 26 +- server/cmd_hash_test.go | 52 ++-- server/cmd_kv_test.go | 38 +-- server/cmd_list_test.go | 40 +-- server/cmd_ttl_test.go | 16 +- server/cmd_zset.go | 2 +- server/cmd_zset_test.go | 114 ++++---- 18 files changed, 147 insertions(+), 2307 deletions(-) delete mode 100644 client/go/redis/commandinfo.go delete mode 100644 client/go/redis/conn.go delete mode 100644 client/go/redis/doc.go delete mode 100644 client/go/redis/log.go delete mode 100644 client/go/redis/pool.go delete mode 100644 client/go/redis/pubsub.go delete mode 100644 client/go/redis/redis.go delete mode 100644 client/go/redis/reply.go delete mode 100644 client/go/redis/scan.go delete mode 100644 client/go/redis/script.go diff --git a/client/go/redis/commandinfo.go b/client/go/redis/commandinfo.go deleted file mode 100644 index 014115d..0000000 --- a/client/go/redis/commandinfo.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2014 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "strings" -) - -const ( - watchState = 1 << iota - multiState - subscribeState - monitorState -) - -type commandInfo struct { - set, clear int -} - -var commandInfos = map[string]commandInfo{ - "WATCH": commandInfo{set: watchState}, - "UNWATCH": commandInfo{clear: watchState}, - "MULTI": commandInfo{set: multiState}, - "EXEC": commandInfo{clear: watchState | multiState}, - "DISCARD": commandInfo{clear: watchState | multiState}, - "PSUBSCRIBE": commandInfo{set: subscribeState}, - "SUBSCRIBE": commandInfo{set: subscribeState}, - "MONITOR": commandInfo{set: monitorState}, -} - -func lookupCommandInfo(commandName string) commandInfo { - return commandInfos[strings.ToUpper(commandName)] -} diff --git a/client/go/redis/conn.go b/client/go/redis/conn.go deleted file mode 100644 index 331d3c6..0000000 --- a/client/go/redis/conn.go +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "net" - "strconv" - "sync" - "time" -) - -// conn is the low-level implementation of Conn -type conn struct { - - // Shared - mu sync.Mutex - pending int - err error - conn net.Conn - - // Read - readTimeout time.Duration - br *bufio.Reader - - // Write - writeTimeout time.Duration - bw *bufio.Writer - - // Scratch space for formatting argument length. - // '*' or '$', length, "\r\n" - lenScratch [32]byte - - // Scratch space for formatting integers and floats. - numScratch [40]byte -} - -// Dial connects to the Redis server at the given network and address. -func Dial(network, address string) (Conn, error) { - c, err := net.Dial(network, address) - if err != nil { - return nil, err - } - return NewConn(c, 0, 0), nil -} - -// DialTimeout acts like Dial but takes timeouts for establishing the -// connection to the server, writing a command and reading a reply. -func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { - var c net.Conn - var err error - if connectTimeout > 0 { - c, err = net.DialTimeout(network, address, connectTimeout) - } else { - c, err = net.Dial(network, address) - } - if err != nil { - return nil, err - } - return NewConn(c, readTimeout, writeTimeout), nil -} - -// NewConn returns a new Redigo connection for the given net connection. -func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { - return &conn{ - conn: netConn, - bw: bufio.NewWriter(netConn), - br: bufio.NewReader(netConn), - readTimeout: readTimeout, - writeTimeout: writeTimeout, - } -} - -func (c *conn) Close() error { - c.mu.Lock() - err := c.err - if c.err == nil { - c.err = errors.New("redigo: closed") - err = c.conn.Close() - } - c.mu.Unlock() - return err -} - -func (c *conn) fatal(err error) error { - c.mu.Lock() - if c.err == nil { - c.err = err - // Close connection to force errors on subsequent calls and to unblock - // other reader or writer. - c.conn.Close() - } - c.mu.Unlock() - return err -} - -func (c *conn) Err() error { - c.mu.Lock() - err := c.err - c.mu.Unlock() - return err -} - -func (c *conn) writeLen(prefix byte, n int) error { - c.lenScratch[len(c.lenScratch)-1] = '\n' - c.lenScratch[len(c.lenScratch)-2] = '\r' - i := len(c.lenScratch) - 3 - for { - c.lenScratch[i] = byte('0' + n%10) - i -= 1 - n = n / 10 - if n == 0 { - break - } - } - c.lenScratch[i] = prefix - _, err := c.bw.Write(c.lenScratch[i:]) - return err -} - -func (c *conn) writeString(s string) error { - c.writeLen('$', len(s)) - c.bw.WriteString(s) - _, err := c.bw.WriteString("\r\n") - return err -} - -func (c *conn) writeBytes(p []byte) error { - c.writeLen('$', len(p)) - c.bw.Write(p) - _, err := c.bw.WriteString("\r\n") - return err -} - -func (c *conn) writeInt64(n int64) error { - return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) -} - -func (c *conn) writeFloat64(n float64) error { - return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) -} - -func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { - c.writeLen('*', 1+len(args)) - err = c.writeString(cmd) - for _, arg := range args { - if err != nil { - break - } - switch arg := arg.(type) { - case string: - err = c.writeString(arg) - case []byte: - err = c.writeBytes(arg) - case int: - err = c.writeInt64(int64(arg)) - case int64: - err = c.writeInt64(arg) - case float64: - err = c.writeFloat64(arg) - case bool: - if arg { - err = c.writeString("1") - } else { - err = c.writeString("0") - } - case nil: - err = c.writeString("") - default: - var buf bytes.Buffer - fmt.Fprint(&buf, arg) - err = c.writeBytes(buf.Bytes()) - } - } - return err -} - -func (c *conn) readLine() ([]byte, error) { - p, err := c.br.ReadSlice('\n') - if err == bufio.ErrBufferFull { - return nil, errors.New("redigo: long response line") - } - if err != nil { - return nil, err - } - i := len(p) - 2 - if i < 0 || p[i] != '\r' { - return nil, errors.New("redigo: bad response line terminator") - } - return p[:i], nil -} - -// parseLen parses bulk string and array lengths. -func parseLen(p []byte) (int, error) { - if len(p) == 0 { - return -1, errors.New("redigo: malformed length") - } - - if p[0] == '-' && len(p) == 2 && p[1] == '1' { - // handle $-1 and $-1 null replies. - return -1, nil - } - - var n int - for _, b := range p { - n *= 10 - if b < '0' || b > '9' { - return -1, errors.New("redigo: illegal bytes in length") - } - n += int(b - '0') - } - - return n, nil -} - -// parseInt parses an integer reply. -func parseInt(p []byte) (interface{}, error) { - if len(p) == 0 { - return 0, errors.New("redigo: malformed integer") - } - - var negate bool - if p[0] == '-' { - negate = true - p = p[1:] - if len(p) == 0 { - return 0, errors.New("redigo: malformed integer") - } - } - - var n int64 - for _, b := range p { - n *= 10 - if b < '0' || b > '9' { - return 0, errors.New("redigo: illegal bytes in length") - } - n += int64(b - '0') - } - - if negate { - n = -n - } - return n, nil -} - -var ( - okReply interface{} = "OK" - pongReply interface{} = "PONG" -) - -func (c *conn) readReply() (interface{}, error) { - line, err := c.readLine() - if err != nil { - return nil, err - } - if len(line) == 0 { - return nil, errors.New("redigo: short response line") - } - switch line[0] { - case '+': - switch { - case len(line) == 3 && line[1] == 'O' && line[2] == 'K': - // Avoid allocation for frequent "+OK" response. - return okReply, nil - case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': - // Avoid allocation in PING command benchmarks :) - return pongReply, nil - default: - return string(line[1:]), nil - } - case '-': - return Error(string(line[1:])), nil - case ':': - return parseInt(line[1:]) - case '$': - n, err := parseLen(line[1:]) - if n < 0 || err != nil { - return nil, err - } - p := make([]byte, n) - _, err = io.ReadFull(c.br, p) - if err != nil { - return nil, err - } - if line, err := c.readLine(); err != nil { - return nil, err - } else if len(line) != 0 { - return nil, errors.New("redigo: bad bulk string format") - } - return p, nil - case '*': - n, err := parseLen(line[1:]) - if n < 0 || err != nil { - return nil, err - } - r := make([]interface{}, n) - for i := range r { - r[i], err = c.readReply() - if err != nil { - return nil, err - } - } - return r, nil - } - return nil, errors.New("redigo: unexpected response line") -} - -func (c *conn) Send(cmd string, args ...interface{}) error { - c.mu.Lock() - c.pending += 1 - c.mu.Unlock() - if c.writeTimeout != 0 { - c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) - } - if err := c.writeCommand(cmd, args); err != nil { - return c.fatal(err) - } - return nil -} - -func (c *conn) Flush() error { - if c.writeTimeout != 0 { - c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) - } - if err := c.bw.Flush(); err != nil { - return c.fatal(err) - } - return nil -} - -func (c *conn) Receive() (reply interface{}, err error) { - c.mu.Lock() - // There can be more receives than sends when using pub/sub. To allow - // normal use of the connection after unsubscribe from all channels, do not - // decrement pending to a negative value. - if c.pending > 0 { - c.pending -= 1 - } - c.mu.Unlock() - if c.readTimeout != 0 { - c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) - } - if reply, err = c.readReply(); err != nil { - return nil, c.fatal(err) - } - if err, ok := reply.(Error); ok { - return nil, err - } - return -} - -func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { - c.mu.Lock() - pending := c.pending - c.pending = 0 - c.mu.Unlock() - - if cmd == "" && pending == 0 { - return nil, nil - } - - if c.writeTimeout != 0 { - c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) - } - - if cmd != "" { - c.writeCommand(cmd, args) - } - - if err := c.bw.Flush(); err != nil { - return nil, c.fatal(err) - } - - if c.readTimeout != 0 { - c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) - } - - if cmd == "" { - reply := make([]interface{}, pending) - for i := range reply { - r, e := c.readReply() - if e != nil { - return nil, c.fatal(e) - } - reply[i] = r - } - return reply, nil - } - - var err error - var reply interface{} - for i := 0; i <= pending; i++ { - var e error - if reply, e = c.readReply(); e != nil { - return nil, c.fatal(e) - } - if e, ok := reply.(Error); ok && err == nil { - err = e - } - } - return reply, err -} diff --git a/client/go/redis/doc.go b/client/go/redis/doc.go deleted file mode 100644 index 63e6ffe..0000000 --- a/client/go/redis/doc.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -// Package redis is a client for the Redis database. -// -// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more -// documentation about this package. -// -// Connections -// -// The Conn interface is the primary interface for working with Redis. -// Applications create connections by calling the Dial, DialWithTimeout or -// NewConn functions. In the future, functions will be added for creating -// sharded and other types of connections. -// -// The application must call the connection Close method when the application -// is done with the connection. -// -// Executing Commands -// -// The Conn interface has a generic method for executing Redis commands: -// -// Do(commandName string, args ...interface{}) (reply interface{}, err error) -// -// The Redis command reference (http://redis.io/commands) lists the available -// commands. An example of using the Redis APPEND command is: -// -// n, err := conn.Do("APPEND", "key", "value") -// -// The Do method converts command arguments to binary strings for transmission -// to the server as follows: -// -// Go Type Conversion -// []byte Sent as is -// string Sent as is -// int, int64 strconv.FormatInt(v) -// float64 strconv.FormatFloat(v, 'g', -1, 64) -// bool true -> "1", false -> "0" -// nil "" -// all other types fmt.Print(v) -// -// Redis command reply types are represented using the following Go types: -// -// Redis type Go type -// error redis.Error -// integer int64 -// simple string string -// bulk string []byte or nil if value not present. -// array []interface{} or nil if value not present. -// -// Use type assertions or the reply helper functions to convert from -// interface{} to the specific Go type for the command result. -// -// Pipelining -// -// Connections support pipelining using the Send, Flush and Receive methods. -// -// Send(commandName string, args ...interface{}) error -// Flush() error -// Receive() (reply interface{}, err error) -// -// Send writes the command to the connection's output buffer. Flush flushes the -// connection's output buffer to the server. Receive reads a single reply from -// the server. The following example shows a simple pipeline. -// -// c.Send("SET", "foo", "bar") -// c.Send("GET", "foo") -// c.Flush() -// c.Receive() // reply from SET -// v, err = c.Receive() // reply from GET -// -// The Do method combines the functionality of the Send, Flush and Receive -// methods. The Do method starts by writing the command and flushing the output -// buffer. Next, the Do method receives all pending replies including the reply -// for the command just sent by Do. If any of the received replies is an error, -// then Do returns the error. If there are no errors, then Do returns the last -// reply. If the command argument to the Do method is "", then the Do method -// will flush the output buffer and receive pending replies without sending a -// command. -// -// Use the Send and Do methods to implement pipelined transactions. -// -// c.Send("MULTI") -// c.Send("INCR", "foo") -// c.Send("INCR", "bar") -// r, err := c.Do("EXEC") -// fmt.Println(r) // prints [1, 1] -// -// Concurrency -// -// Connections support a single concurrent caller to the write methods (Send, -// Flush) and a single concurrent caller to the read method (Receive). Because -// Do method combines the functionality of Send, Flush and Receive, the Do -// method cannot be called concurrently with the other methods. -// -// For full concurrent access to Redis, use the thread-safe Pool to get and -// release connections from within a goroutine. -// -// Publish and Subscribe -// -// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. -// -// c.Send("SUBSCRIBE", "example") -// c.Flush() -// for { -// reply, err := c.Receive() -// if err != nil { -// return err -// } -// // process pushed message -// } -// -// The PubSubConn type wraps a Conn with convenience methods for implementing -// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods -// send and flush a subscription management command. The receive method -// converts a pushed message to convenient types for use in a type switch. -// -// psc := PubSubConn{c} -// psc.Subscribe("example") -// for { -// switch v := psc.Receive().(type) { -// case redis.Message: -// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) -// case redis.Subscription: -// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) -// case error: -// return v -// } -// } -// -// Reply Helpers -// -// The Bool, Int, Bytes, String, Strings and Values functions convert a reply -// to a value of a specific type. To allow convenient wrapping of calls to the -// connection Do and Receive methods, the functions take a second argument of -// type error. If the error is non-nil, then the helper function returns the -// error. If the error is nil, the function converts the reply to the specified -// type: -// -// exists, err := redis.Bool(c.Do("EXISTS", "foo")) -// if err != nil { -// // handle error return from c.Do or type conversion error. -// } -// -// The Scan function converts elements of a array reply to Go types: -// -// var value1 int -// var value2 string -// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) -// if err != nil { -// // handle error -// } -// if _, err := redis.Scan(reply, &value1, &value2); err != nil { -// // handle error -// } -package redis diff --git a/client/go/redis/log.go b/client/go/redis/log.go deleted file mode 100644 index 129b86d..0000000 --- a/client/go/redis/log.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bytes" - "fmt" - "log" -) - -// NewLoggingConn returns a logging wrapper around a connection. -func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { - if prefix != "" { - prefix = prefix + "." - } - return &loggingConn{conn, logger, prefix} -} - -type loggingConn struct { - Conn - logger *log.Logger - prefix string -} - -func (c *loggingConn) Close() error { - err := c.Conn.Close() - var buf bytes.Buffer - fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) - c.logger.Output(2, buf.String()) - return err -} - -func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { - const chop = 32 - switch v := v.(type) { - case []byte: - if len(v) > chop { - fmt.Fprintf(buf, "%q...", v[:chop]) - } else { - fmt.Fprintf(buf, "%q", v) - } - case string: - if len(v) > chop { - fmt.Fprintf(buf, "%q...", v[:chop]) - } else { - fmt.Fprintf(buf, "%q", v) - } - case []interface{}: - if len(v) == 0 { - buf.WriteString("[]") - } else { - sep := "[" - fin := "]" - if len(v) > chop { - v = v[:chop] - fin = "...]" - } - for _, vv := range v { - buf.WriteString(sep) - c.printValue(buf, vv) - sep = ", " - } - buf.WriteString(fin) - } - default: - fmt.Fprint(buf, v) - } -} - -func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { - var buf bytes.Buffer - fmt.Fprintf(&buf, "%s%s(", c.prefix, method) - if method != "Receive" { - buf.WriteString(commandName) - for _, arg := range args { - buf.WriteString(", ") - c.printValue(&buf, arg) - } - } - buf.WriteString(") -> (") - if method != "Send" { - c.printValue(&buf, reply) - buf.WriteString(", ") - } - fmt.Fprintf(&buf, "%v)", err) - c.logger.Output(3, buf.String()) -} - -func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { - reply, err := c.Conn.Do(commandName, args...) - c.print("Do", commandName, args, reply, err) - return reply, err -} - -func (c *loggingConn) Send(commandName string, args ...interface{}) error { - err := c.Conn.Send(commandName, args...) - c.print("Send", commandName, args, nil, err) - return err -} - -func (c *loggingConn) Receive() (interface{}, error) { - reply, err := c.Conn.Receive() - c.print("Receive", "", nil, reply, err) - return reply, err -} diff --git a/client/go/redis/pool.go b/client/go/redis/pool.go deleted file mode 100644 index 2e2dac9..0000000 --- a/client/go/redis/pool.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bytes" - "container/list" - "crypto/rand" - "crypto/sha1" - "errors" - "io" - "strconv" - "sync" - "time" -) - -var nowFunc = time.Now // for testing - -// ErrPoolExhausted is returned from a pool connection method (Do, Send, -// Receive, Flush, Err) when the maximum number of database connections in the -// pool has been reached. -var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") - -var errPoolClosed = errors.New("redigo: connection pool closed") - -// Pool maintains a pool of connections. The application calls the Get method -// to get a connection from the pool and the connection's Close method to -// return the connection's resources to the pool. -// -// The following example shows how to use a pool in a web application. The -// application creates a pool at application startup and makes it available to -// request handlers using a global variable. -// -// func newPool(server, password string) *redis.Pool { -// return &redis.Pool{ -// MaxIdle: 3, -// IdleTimeout: 240 * time.Second, -// Dial: func () (redis.Conn, error) { -// c, err := redis.Dial("tcp", server) -// if err != nil { -// return nil, err -// } -// if _, err := c.Do("AUTH", password); err != nil { -// c.Close() -// return nil, err -// } -// return c, err -// }, -// TestOnBorrow: func(c redis.Conn, t time.Time) error { -// _, err := c.Do("PING") -// return err -// }, -// } -// } -// -// var ( -// pool *redis.Pool -// redisServer = flag.String("redisServer", ":6379", "") -// redisPassword = flag.String("redisPassword", "", "") -// ) -// -// func main() { -// flag.Parse() -// pool = newPool(*redisServer, *redisPassword) -// ... -// } -// -// A request handler gets a connection from the pool and closes the connection -// when the handler is done: -// -// func serveHome(w http.ResponseWriter, r *http.Request) { -// conn := pool.Get() -// defer conn.Close() -// .... -// } -// -type Pool struct { - - // Dial is an application supplied function for creating new connections. - Dial func() (Conn, error) - - // TestOnBorrow is an optional application supplied function for checking - // the health of an idle connection before the connection is used again by - // the application. Argument t is the time that the connection was returned - // to the pool. If the function returns an error, then the connection is - // closed. - TestOnBorrow func(c Conn, t time.Time) error - - // Maximum number of idle connections in the pool. - MaxIdle int - - // Maximum number of connections allocated by the pool at a given time. - // When zero, there is no limit on the number of connections in the pool. - MaxActive int - - // Close connections after remaining idle for this duration. If the value - // is zero, then idle connections are not closed. Applications should set - // the timeout to a value less than the server's timeout. - IdleTimeout time.Duration - - // mu protects fields defined below. - mu sync.Mutex - closed bool - active int - - // Stack of idleConn with most recently used at the front. - idle list.List -} - -type idleConn struct { - c Conn - t time.Time -} - -// NewPool is a convenience function for initializing a pool. -func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { - return &Pool{Dial: newFn, MaxIdle: maxIdle} -} - -// Get gets a connection. The application must close the returned connection. -// The connection acquires an underlying connection on the first call to the -// connection Do, Send, Receive, Flush or Err methods. An application can force -// the connection to acquire an underlying connection without executing a Redis -// command by calling the Err method. -func (p *Pool) Get() Conn { - return &pooledConnection{p: p} -} - -// ActiveCount returns the number of active connections in the pool. -func (p *Pool) ActiveCount() int { - p.mu.Lock() - active := p.active - p.mu.Unlock() - return active -} - -// Close releases the resources used by the pool. -func (p *Pool) Close() error { - p.mu.Lock() - idle := p.idle - p.idle.Init() - p.closed = true - p.active -= idle.Len() - p.mu.Unlock() - for e := idle.Front(); e != nil; e = e.Next() { - e.Value.(idleConn).c.Close() - } - return nil -} - -// get prunes stale connections and returns a connection from the idle list or -// creates a new connection. -func (p *Pool) get() (Conn, error) { - p.mu.Lock() - - if p.closed { - p.mu.Unlock() - return nil, errors.New("redigo: get on closed pool") - } - - // Prune stale connections. - - if timeout := p.IdleTimeout; timeout > 0 { - for i, n := 0, p.idle.Len(); i < n; i++ { - e := p.idle.Back() - if e == nil { - break - } - ic := e.Value.(idleConn) - if ic.t.Add(timeout).After(nowFunc()) { - break - } - p.idle.Remove(e) - p.active -= 1 - p.mu.Unlock() - ic.c.Close() - p.mu.Lock() - } - } - - // Get idle connection. - - for i, n := 0, p.idle.Len(); i < n; i++ { - e := p.idle.Front() - if e == nil { - break - } - ic := e.Value.(idleConn) - p.idle.Remove(e) - test := p.TestOnBorrow - p.mu.Unlock() - if test == nil || test(ic.c, ic.t) == nil { - return ic.c, nil - } - ic.c.Close() - p.mu.Lock() - p.active -= 1 - } - - if p.MaxActive > 0 && p.active >= p.MaxActive { - p.mu.Unlock() - return nil, ErrPoolExhausted - } - - // No idle connection, create new. - - dial := p.Dial - p.active += 1 - p.mu.Unlock() - c, err := dial() - if err != nil { - p.mu.Lock() - p.active -= 1 - p.mu.Unlock() - c = nil - } - return c, err -} - -func (p *Pool) put(c Conn, forceClose bool) error { - if c.Err() == nil && !forceClose { - p.mu.Lock() - if !p.closed { - p.idle.PushFront(idleConn{t: nowFunc(), c: c}) - if p.idle.Len() > p.MaxIdle { - c = p.idle.Remove(p.idle.Back()).(idleConn).c - } else { - c = nil - } - } - p.mu.Unlock() - } - if c != nil { - p.mu.Lock() - p.active -= 1 - p.mu.Unlock() - return c.Close() - } - return nil -} - -type pooledConnection struct { - c Conn - err error - p *Pool - state int -} - -func (c *pooledConnection) get() error { - if c.err == nil && c.c == nil { - c.c, c.err = c.p.get() - } - return c.err -} - -var ( - sentinel []byte - sentinelOnce sync.Once -) - -func initSentinel() { - p := make([]byte, 64) - if _, err := rand.Read(p); err == nil { - sentinel = p - } else { - h := sha1.New() - io.WriteString(h, "Oops, rand failed. Use time instead.") - io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) - sentinel = h.Sum(nil) - } -} - -func (c *pooledConnection) Close() (err error) { - if c.c != nil { - if c.state&multiState != 0 { - c.c.Send("DISCARD") - c.state &^= (multiState | watchState) - } else if c.state&watchState != 0 { - c.c.Send("UNWATCH") - c.state &^= watchState - } - if c.state&subscribeState != 0 { - c.c.Send("UNSUBSCRIBE") - c.c.Send("PUNSUBSCRIBE") - // To detect the end of the message stream, ask the server to echo - // a sentinel value and read until we see that value. - sentinelOnce.Do(initSentinel) - c.c.Send("ECHO", sentinel) - c.c.Flush() - for { - p, err := c.c.Receive() - if err != nil { - break - } - if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { - c.state &^= subscribeState - break - } - } - } - c.c.Do("") - c.p.put(c.c, c.state != 0) - c.c = nil - c.err = errPoolClosed - } - return err -} - -func (c *pooledConnection) Err() error { - if err := c.get(); err != nil { - return err - } - return c.c.Err() -} - -func (c *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { - if err := c.get(); err != nil { - return nil, err - } - ci := lookupCommandInfo(commandName) - c.state = (c.state | ci.set) &^ ci.clear - return c.c.Do(commandName, args...) -} - -func (c *pooledConnection) Send(commandName string, args ...interface{}) error { - if err := c.get(); err != nil { - return err - } - ci := lookupCommandInfo(commandName) - c.state = (c.state | ci.set) &^ ci.clear - return c.c.Send(commandName, args...) -} - -func (c *pooledConnection) Flush() error { - if err := c.get(); err != nil { - return err - } - return c.c.Flush() -} - -func (c *pooledConnection) Receive() (reply interface{}, err error) { - if err := c.get(); err != nil { - return nil, err - } - return c.c.Receive() -} diff --git a/client/go/redis/pubsub.go b/client/go/redis/pubsub.go deleted file mode 100644 index f079042..0000000 --- a/client/go/redis/pubsub.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "errors" -) - -// Subscription represents a subscribe or unsubscribe notification. -type Subscription struct { - - // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" - Kind string - - // The channel that was changed. - Channel string - - // The current number of subscriptions for connection. - Count int -} - -// Message represents a message notification. -type Message struct { - - // The originating channel. - Channel string - - // The message data. - Data []byte -} - -// PMessage represents a pmessage notification. -type PMessage struct { - - // The matched pattern. - Pattern string - - // The originating channel. - Channel string - - // The message data. - Data []byte -} - -// PubSubConn wraps a Conn with convenience methods for subscribers. -type PubSubConn struct { - Conn Conn -} - -// Close closes the connection. -func (c PubSubConn) Close() error { - return c.Conn.Close() -} - -// Subscribe subscribes the connection to the specified channels. -func (c PubSubConn) Subscribe(channel ...interface{}) error { - c.Conn.Send("SUBSCRIBE", channel...) - return c.Conn.Flush() -} - -// PSubscribe subscribes the connection to the given patterns. -func (c PubSubConn) PSubscribe(channel ...interface{}) error { - c.Conn.Send("PSUBSCRIBE", channel...) - return c.Conn.Flush() -} - -// Unsubscribe unsubscribes the connection from the given channels, or from all -// of them if none is given. -func (c PubSubConn) Unsubscribe(channel ...interface{}) error { - c.Conn.Send("UNSUBSCRIBE", channel...) - return c.Conn.Flush() -} - -// PUnsubscribe unsubscribes the connection from the given patterns, or from all -// of them if none is given. -func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { - c.Conn.Send("PUNSUBSCRIBE", channel...) - return c.Conn.Flush() -} - -// Receive returns a pushed message as a Subscription, Message, PMessage or -// error. The return value is intended to be used directly in a type switch as -// illustrated in the PubSubConn example. -func (c PubSubConn) Receive() interface{} { - reply, err := Values(c.Conn.Receive()) - if err != nil { - return err - } - - var kind string - reply, err = Scan(reply, &kind) - if err != nil { - return err - } - - switch kind { - case "message": - var m Message - if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { - return err - } - return m - case "pmessage": - var pm PMessage - if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { - return err - } - return pm - case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": - s := Subscription{Kind: kind} - if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { - return err - } - return s - } - return errors.New("redigo: unknown pubsub notification") -} diff --git a/client/go/redis/redis.go b/client/go/redis/redis.go deleted file mode 100644 index c90a48e..0000000 --- a/client/go/redis/redis.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -// Error represents an error returned in a command reply. -type Error string - -func (err Error) Error() string { return string(err) } - -// Conn represents a connection to a Redis server. -type Conn interface { - // Close closes the connection. - Close() error - - // Err returns a non-nil value if the connection is broken. The returned - // value is either the first non-nil value returned from the underlying - // network connection or a protocol parsing error. Applications should - // close broken connections. - Err() error - - // Do sends a command to the server and returns the received reply. - Do(commandName string, args ...interface{}) (reply interface{}, err error) - - // Send writes the command to the client's output buffer. - Send(commandName string, args ...interface{}) error - - // Flush flushes the output buffer to the Redis server. - Flush() error - - // Receive receives a single reply from the Redis server - Receive() (reply interface{}, err error) -} diff --git a/client/go/redis/reply.go b/client/go/redis/reply.go deleted file mode 100644 index 161a147..0000000 --- a/client/go/redis/reply.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "errors" - "fmt" - "strconv" -) - -// ErrNil indicates that a reply value is nil. -var ErrNil = errors.New("redigo: nil returned") - -// Int is a helper that converts a command reply to an integer. If err is not -// equal to nil, then Int returns 0, err. Otherwise, Int converts the -// reply to an int as follows: -// -// Reply type Result -// integer int(reply), nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Int(reply interface{}, err error) (int, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - x := int(reply) - if int64(x) != reply { - return 0, strconv.ErrRange - } - return x, nil - case []byte: - n, err := strconv.ParseInt(string(reply), 10, 0) - return int(n), err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) -} - -// Int64 is a helper that converts a command reply to 64 bit integer. If err is -// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the -// reply to an int64 as follows: -// -// Reply type Result -// integer reply, nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Int64(reply interface{}, err error) (int64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - return reply, nil - case []byte: - n, err := strconv.ParseInt(string(reply), 10, 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) -} - -var errNegativeInt = errors.New("redigo: unexpected value for Uint64") - -// Uint64 is a helper that converts a command reply to 64 bit integer. If err is -// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the -// reply to an int64 as follows: -// -// Reply type Result -// integer reply, nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Uint64(reply interface{}, err error) (uint64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - if reply < 0 { - return 0, errNegativeInt - } - return uint64(reply), nil - case []byte: - n, err := strconv.ParseUint(string(reply), 10, 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) -} - -// Float64 is a helper that converts a command reply to 64 bit float. If err is -// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts -// the reply to an int as follows: -// -// Reply type Result -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Float64(reply interface{}, err error) (float64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case []byte: - n, err := strconv.ParseFloat(string(reply), 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) -} - -// String is a helper that converts a command reply to a string. If err is not -// equal to nil, then String returns "", err. Otherwise String converts the -// reply to a string as follows: -// -// Reply type Result -// bulk string string(reply), nil -// simple string reply, nil -// nil "", ErrNil -// other "", error -func String(reply interface{}, err error) (string, error) { - if err != nil { - return "", err - } - switch reply := reply.(type) { - case []byte: - return string(reply), nil - case string: - return reply, nil - case nil: - return "", ErrNil - case Error: - return "", reply - } - return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) -} - -// Bytes is a helper that converts a command reply to a slice of bytes. If err -// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts -// the reply to a slice of bytes as follows: -// -// Reply type Result -// bulk string reply, nil -// simple string []byte(reply), nil -// nil nil, ErrNil -// other nil, error -func Bytes(reply interface{}, err error) ([]byte, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []byte: - return reply, nil - case string: - return []byte(reply), nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) -} - -// Bool is a helper that converts a command reply to a boolean. If err is not -// equal to nil, then Bool returns false, err. Otherwise Bool converts the -// reply to boolean as follows: -// -// Reply type Result -// integer value != 0, nil -// bulk string strconv.ParseBool(reply) -// nil false, ErrNil -// other false, error -func Bool(reply interface{}, err error) (bool, error) { - if err != nil { - return false, err - } - switch reply := reply.(type) { - case int64: - return reply != 0, nil - case []byte: - return strconv.ParseBool(string(reply)) - case nil: - return false, ErrNil - case Error: - return false, reply - } - return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) -} - -// MultiBulk is deprecated. Use Values. -func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } - -// Values is a helper that converts an array command reply to a []interface{}. -// If err is not equal to nil, then Values returns nil, err. Otherwise, Values -// converts the reply as follows: -// -// Reply type Result -// array reply, nil -// nil nil, ErrNil -// other nil, error -func Values(reply interface{}, err error) ([]interface{}, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []interface{}: - return reply, nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) -} - -// Strings is a helper that converts an array command reply to a []string. If -// err is not equal to nil, then Strings returns nil, err. If one of the array -// items is not a bulk string or nil, then Strings returns an error. -func Strings(reply interface{}, err error) ([]string, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []interface{}: - result := make([]string, len(reply)) - for i := range reply { - if reply[i] == nil { - continue - } - p, ok := reply[i].([]byte) - if !ok { - return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) - } - result[i] = string(p) - } - return result, nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) -} diff --git a/client/go/redis/scan.go b/client/go/redis/scan.go deleted file mode 100644 index 8c9cfa1..0000000 --- a/client/go/redis/scan.go +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "sync" -) - -func ensureLen(d reflect.Value, n int) { - if n > d.Cap() { - d.Set(reflect.MakeSlice(d.Type(), n, n)) - } else { - d.SetLen(n) - } -} - -func cannotConvert(d reflect.Value, s interface{}) error { - return fmt.Errorf("redigo: Scan cannot convert from %s to %s", - reflect.TypeOf(s), d.Type()) -} - -func convertAssignBytes(d reflect.Value, s []byte) (err error) { - switch d.Type().Kind() { - case reflect.Float32, reflect.Float64: - var x float64 - x, err = strconv.ParseFloat(string(s), d.Type().Bits()) - d.SetFloat(x) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - var x int64 - x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) - d.SetInt(x) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - var x uint64 - x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) - d.SetUint(x) - case reflect.Bool: - var x bool - x, err = strconv.ParseBool(string(s)) - d.SetBool(x) - case reflect.String: - d.SetString(string(s)) - case reflect.Slice: - if d.Type().Elem().Kind() != reflect.Uint8 { - err = cannotConvert(d, s) - } else { - d.SetBytes(s) - } - default: - err = cannotConvert(d, s) - } - return -} - -func convertAssignInt(d reflect.Value, s int64) (err error) { - switch d.Type().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - d.SetInt(s) - if d.Int() != s { - err = strconv.ErrRange - d.SetInt(0) - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if s < 0 { - err = strconv.ErrRange - } else { - x := uint64(s) - d.SetUint(x) - if d.Uint() != x { - err = strconv.ErrRange - d.SetUint(0) - } - } - case reflect.Bool: - d.SetBool(s != 0) - default: - err = cannotConvert(d, s) - } - return -} - -func convertAssignValue(d reflect.Value, s interface{}) (err error) { - switch s := s.(type) { - case []byte: - err = convertAssignBytes(d, s) - case int64: - err = convertAssignInt(d, s) - default: - err = cannotConvert(d, s) - } - return err -} - -func convertAssignValues(d reflect.Value, s []interface{}) error { - if d.Type().Kind() != reflect.Slice { - return cannotConvert(d, s) - } - ensureLen(d, len(s)) - for i := 0; i < len(s); i++ { - if err := convertAssignValue(d.Index(i), s[i]); err != nil { - return err - } - } - return nil -} - -func convertAssign(d interface{}, s interface{}) (err error) { - // Handle the most common destination types using type switches and - // fall back to reflection for all other types. - switch s := s.(type) { - case nil: - // ingore - case []byte: - switch d := d.(type) { - case *string: - *d = string(s) - case *int: - *d, err = strconv.Atoi(string(s)) - case *bool: - *d, err = strconv.ParseBool(string(s)) - case *[]byte: - *d = s - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignBytes(d.Elem(), s) - } - } - case int64: - switch d := d.(type) { - case *int: - x := int(s) - if int64(x) != s { - err = strconv.ErrRange - x = 0 - } - *d = x - case *bool: - *d = s != 0 - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignInt(d.Elem(), s) - } - } - case []interface{}: - switch d := d.(type) { - case *[]interface{}: - *d = s - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignValues(d.Elem(), s) - } - } - case Error: - err = s - default: - err = cannotConvert(reflect.ValueOf(d), s) - } - return -} - -// Scan copies from src to the values pointed at by dest. -// -// The values pointed at by dest must be an integer, float, boolean, string, -// []byte, interface{} or slices of these types. Scan uses the standard strconv -// package to convert bulk strings to numeric and boolean types. -// -// If a dest value is nil, then the corresponding src value is skipped. -// -// If a src element is nil, then the corresponding dest value is not modified. -// -// To enable easy use of Scan in a loop, Scan returns the slice of src -// following the copied values. -func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { - if len(src) < len(dest) { - return nil, errors.New("redigo: Scan array short") - } - var err error - for i, d := range dest { - err = convertAssign(d, src[i]) - if err != nil { - break - } - } - return src[len(dest):], err -} - -type fieldSpec struct { - name string - index []int - //omitEmpty bool -} - -type structSpec struct { - m map[string]*fieldSpec - l []*fieldSpec -} - -func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { - return ss.m[string(name)] -} - -func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - switch { - case f.PkgPath != "": - // Ignore unexported fields. - case f.Anonymous: - // TODO: Handle pointers. Requires change to decoder and - // protection against infinite recursion. - if f.Type.Kind() == reflect.Struct { - compileStructSpec(f.Type, depth, append(index, i), ss) - } - default: - fs := &fieldSpec{name: f.Name} - tag := f.Tag.Get("redis") - p := strings.Split(tag, ",") - if len(p) > 0 { - if p[0] == "-" { - continue - } - if len(p[0]) > 0 { - fs.name = p[0] - } - for _, s := range p[1:] { - switch s { - //case "omitempty": - // fs.omitempty = true - default: - panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) - } - } - } - d, found := depth[fs.name] - if !found { - d = 1 << 30 - } - switch { - case len(index) == d: - // At same depth, remove from result. - delete(ss.m, fs.name) - j := 0 - for i := 0; i < len(ss.l); i++ { - if fs.name != ss.l[i].name { - ss.l[j] = ss.l[i] - j += 1 - } - } - ss.l = ss.l[:j] - case len(index) < d: - fs.index = make([]int, len(index)+1) - copy(fs.index, index) - fs.index[len(index)] = i - depth[fs.name] = len(index) - ss.m[fs.name] = fs - ss.l = append(ss.l, fs) - } - } - } -} - -var ( - structSpecMutex sync.RWMutex - structSpecCache = make(map[reflect.Type]*structSpec) - defaultFieldSpec = &fieldSpec{} -) - -func structSpecForType(t reflect.Type) *structSpec { - - structSpecMutex.RLock() - ss, found := structSpecCache[t] - structSpecMutex.RUnlock() - if found { - return ss - } - - structSpecMutex.Lock() - defer structSpecMutex.Unlock() - ss, found = structSpecCache[t] - if found { - return ss - } - - ss = &structSpec{m: make(map[string]*fieldSpec)} - compileStructSpec(t, make(map[string]int), nil, ss) - structSpecCache[t] = ss - return ss -} - -var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") - -// ScanStruct scans alternating names and values from src to a struct. The -// HGETALL and CONFIG GET commands return replies in this format. -// -// ScanStruct uses exported field names to match values in the response. Use -// 'redis' field tag to override the name: -// -// Field int `redis:"myName"` -// -// Fields with the tag redis:"-" are ignored. -// -// Integer, float, boolean, string and []byte fields are supported. Scan uses the -// standard strconv package to convert bulk string values to numeric and -// boolean types. -// -// If a src element is nil, then the corresponding field is not modified. -func ScanStruct(src []interface{}, dest interface{}) error { - d := reflect.ValueOf(dest) - if d.Kind() != reflect.Ptr || d.IsNil() { - return errScanStructValue - } - d = d.Elem() - if d.Kind() != reflect.Struct { - return errScanStructValue - } - ss := structSpecForType(d.Type()) - - if len(src)%2 != 0 { - return errors.New("redigo: ScanStruct expects even number of values in values") - } - - for i := 0; i < len(src); i += 2 { - s := src[i+1] - if s == nil { - continue - } - name, ok := src[i].([]byte) - if !ok { - return errors.New("redigo: ScanStruct key not a bulk string value") - } - fs := ss.fieldSpec(name) - if fs == nil { - continue - } - if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err - } - } - return nil -} - -var ( - errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") -) - -// ScanSlice scans src to the slice pointed to by dest. The elements the dest -// slice must be integer, float, boolean, string, struct or pointer to struct -// values. -// -// Struct fields must be integer, float, boolean or string values. All struct -// fields are used unless a subset is specified using fieldNames. -func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { - d := reflect.ValueOf(dest) - if d.Kind() != reflect.Ptr || d.IsNil() { - return errScanSliceValue - } - d = d.Elem() - if d.Kind() != reflect.Slice { - return errScanSliceValue - } - - isPtr := false - t := d.Type().Elem() - if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { - isPtr = true - t = t.Elem() - } - - if t.Kind() != reflect.Struct { - ensureLen(d, len(src)) - for i, s := range src { - if s == nil { - continue - } - if err := convertAssignValue(d.Index(i), s); err != nil { - return err - } - } - return nil - } - - ss := structSpecForType(t) - fss := ss.l - if len(fieldNames) > 0 { - fss = make([]*fieldSpec, len(fieldNames)) - for i, name := range fieldNames { - fss[i] = ss.m[name] - if fss[i] == nil { - return errors.New("redigo: ScanSlice bad field name " + name) - } - } - } - - if len(fss) == 0 { - return errors.New("redigo: ScanSlice no struct fields") - } - - n := len(src) / len(fss) - if n*len(fss) != len(src) { - return errors.New("redigo: ScanSlice length not a multiple of struct field count") - } - - ensureLen(d, n) - for i := 0; i < n; i++ { - d := d.Index(i) - if isPtr { - if d.IsNil() { - d.Set(reflect.New(t)) - } - d = d.Elem() - } - for j, fs := range fss { - s := src[i*len(fss)+j] - if s == nil { - continue - } - if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err - } - } - } - return nil -} - -// Args is a helper for constructing command arguments from structured values. -type Args []interface{} - -// Add returns the result of appending value to args. -func (args Args) Add(value ...interface{}) Args { - return append(args, value...) -} - -// AddFlat returns the result of appending the flattened value of v to args. -// -// Maps are flattened by appending the alternating keys and map values to args. -// -// Slices are flattened by appending the slice elements to args. -// -// Structs are flattened by appending the alternating names and values of -// exported fields to args. If v is a nil struct pointer, then nothing is -// appended. The 'redis' field tag overrides struct field names. See ScanStruct -// for more information on the use of the 'redis' field tag. -// -// Other types are appended to args as is. -func (args Args) AddFlat(v interface{}) Args { - rv := reflect.ValueOf(v) - switch rv.Kind() { - case reflect.Struct: - args = flattenStruct(args, rv) - case reflect.Slice: - for i := 0; i < rv.Len(); i++ { - args = append(args, rv.Index(i).Interface()) - } - case reflect.Map: - for _, k := range rv.MapKeys() { - args = append(args, k.Interface(), rv.MapIndex(k).Interface()) - } - case reflect.Ptr: - if rv.Type().Elem().Kind() == reflect.Struct { - if !rv.IsNil() { - args = flattenStruct(args, rv.Elem()) - } - } else { - args = append(args, v) - } - default: - args = append(args, v) - } - return args -} - -func flattenStruct(args Args, v reflect.Value) Args { - ss := structSpecForType(v.Type()) - for _, fs := range ss.l { - fv := v.FieldByIndex(fs.index) - args = append(args, fs.name, fv.Interface()) - } - return args -} diff --git a/client/go/redis/script.go b/client/go/redis/script.go deleted file mode 100644 index 2417753..0000000 --- a/client/go/redis/script.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "crypto/sha1" - "encoding/hex" - "io" - "strings" -) - -// Script encapsulates the source, hash and key count for a Lua script. See -// http://redis.io/commands/eval for information on scripts in Redis. -type Script struct { - keyCount int - src string - hash string -} - -// NewScript returns a new script object. If keyCount is greater than or equal -// to zero, then the count is automatically inserted in the EVAL command -// argument list. If keyCount is less than zero, then the application supplies -// the count as the first value in the keysAndArgs argument to the Do, Send and -// SendHash methods. -func NewScript(keyCount int, src string) *Script { - h := sha1.New() - io.WriteString(h, src) - return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} -} - -func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { - var args []interface{} - if s.keyCount < 0 { - args = make([]interface{}, 1+len(keysAndArgs)) - args[0] = spec - copy(args[1:], keysAndArgs) - } else { - args = make([]interface{}, 2+len(keysAndArgs)) - args[0] = spec - args[1] = s.keyCount - copy(args[2:], keysAndArgs) - } - return args -} - -// Do evalutes the script. Under the covers, Do optimistically evaluates the -// script using the EVALSHA command. If the command fails because the script is -// not loaded, then Do evaluates the script using the EVAL command (thus -// causing the script to load). -func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { - v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) - if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { - v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) - } - return v, err -} - -// SendHash evaluates the script without waiting for the reply. The script is -// evaluated with the EVALSHA command. The application must ensure that the -// script is loaded by a previous call to Send, Do or Load methods. -func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { - return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) -} - -// Send evaluates the script without waiting for the reply. -func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { - return c.Send("EVAL", s.args(s.src, keysAndArgs)...) -} - -// Load loads the script without evaluating it. -func (s *Script) Load(c Conn) error { - _, err := c.Do("SCRIPT", "LOAD", s.src) - return err -} diff --git a/cmd/ledis-benchmark/main.go b/cmd/ledis-benchmark/main.go index 9209dd7..4f01242 100644 --- a/cmd/ledis-benchmark/main.go +++ b/cmd/ledis-benchmark/main.go @@ -3,7 +3,7 @@ package main import ( "flag" "fmt" - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "math/rand" "sync" "time" @@ -16,14 +16,14 @@ var clients = flag.Int("c", 50, "number of clients") var wg sync.WaitGroup -var pool *redis.Pool +var client *ledis.Client var loop int = 0 func waitBench(cmd string, args ...interface{}) { defer wg.Done() - c := pool.Get() + c := client.Get() defer c.Close() for i := 0; i < loop; i++ { @@ -234,15 +234,9 @@ func main() { addr := fmt.Sprintf("%s:%d", *ip, *port) - f := func() (redis.Conn, error) { - c, err := redis.Dial("tcp", addr) - if err != nil { - return nil, err - } - - return c, nil - } - pool = redis.NewPool(f, *clients) + cfg := new(ledis.Config) + cfg.Addr = addr + client = ledis.NewClient(cfg) benchSet() benchIncr() diff --git a/server/app_test.go b/server/app_test.go index 6ae909a..b4db346 100644 --- a/server/app_test.go +++ b/server/app_test.go @@ -1,7 +1,7 @@ package server import ( - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "os" "sync" "testing" @@ -10,29 +10,23 @@ import ( var testAppOnce sync.Once var testApp *App -var testPool *redis.Pool +var testLedisClient *ledis.Client -func newTestRedisPool() { - f := func() (redis.Conn, error) { - c, err := redis.Dial("tcp", "127.0.0.1:16380") - if err != nil { - return nil, err - } - - return c, nil - } - - testPool = redis.NewPool(f, 4) +func newTestLedisClient() { + cfg := new(ledis.Config) + cfg.Addr = "127.0.0.1:16380" + cfg.MaxIdleConns = 4 + testLedisClient = ledis.NewClient(cfg) } -func getTestConn() redis.Conn { +func getTestConn() *ledis.Conn { startTestApp() - return testPool.Get() + return testLedisClient.Get() } func startTestApp() { f := func() { - newTestRedisPool() + newTestLedisClient() os.RemoveAll("/tmp/testdb") diff --git a/server/cmd_hash_test.go b/server/cmd_hash_test.go index 3894386..6b3cae3 100644 --- a/server/cmd_hash_test.go +++ b/server/cmd_hash_test.go @@ -2,7 +2,7 @@ package server import ( "fmt" - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "strconv" "testing" ) @@ -12,43 +12,43 @@ func TestHash(t *testing.T) { defer c.Close() key := []byte("a") - if n, err := redis.Int(c.Do("hset", key, 1, 0)); err != nil { + if n, err := ledis.Int(c.Do("hset", key, 1, 0)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hexists", key, 1)); err != nil { + if n, err := ledis.Int(c.Do("hexists", key, 1)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hexists", key, -1)); err != nil { + if n, err := ledis.Int(c.Do("hexists", key, -1)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hget", key, 1)); err != nil { + if n, err := ledis.Int(c.Do("hget", key, 1)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hset", key, 1, 1)); err != nil { + if n, err := ledis.Int(c.Do("hset", key, 1, 1)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hget", key, 1)); err != nil { + if n, err := ledis.Int(c.Do("hget", key, 1)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) @@ -84,19 +84,19 @@ func TestHashM(t *testing.T) { defer c.Close() key := []byte("b") - if ok, err := redis.String(c.Do("hmset", key, 1, 1, 2, 2, 3, 3)); err != nil { + if ok, err := ledis.String(c.Do("hmset", key, 1, 1, 2, 2, 3, 3)); err != nil { t.Fatal(err) } else if ok != OK { t.Fatal(ok) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if v, err := redis.MultiBulk(c.Do("hmget", key, 1, 2, 3, 4)); err != nil { + if v, err := ledis.MultiBulk(c.Do("hmget", key, 1, 2, 3, 4)); err != nil { t.Fatal(err) } else { if err := testHashArray(v, 1, 2, 3, 0); err != nil { @@ -104,19 +104,19 @@ func TestHashM(t *testing.T) { } } - if n, err := redis.Int(c.Do("hdel", key, 1, 2, 3, 4)); err != nil { + if n, err := ledis.Int(c.Do("hdel", key, 1, 2, 3, 4)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if v, err := redis.MultiBulk(c.Do("hmget", key, 1, 2, 3, 4)); err != nil { + if v, err := ledis.MultiBulk(c.Do("hmget", key, 1, 2, 3, 4)); err != nil { t.Fatal(err) } else { if err := testHashArray(v, 0, 0, 0, 0); err != nil { @@ -124,7 +124,7 @@ func TestHashM(t *testing.T) { } } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) @@ -136,31 +136,31 @@ func TestHashIncr(t *testing.T) { defer c.Close() key := []byte("c") - if n, err := redis.Int(c.Do("hincrby", key, 1, 1)); err != nil { + if n, err := ledis.Int(c.Do("hincrby", key, 1, 1)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(err) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hincrby", key, 1, 10)); err != nil { + if n, err := ledis.Int(c.Do("hincrby", key, 1, 10)); err != nil { t.Fatal(err) } else if n != 11 { t.Fatal(err) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hincrby", key, 1, -11)); err != nil { + if n, err := ledis.Int(c.Do("hincrby", key, 1, -11)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(err) @@ -174,13 +174,13 @@ func TestHashGetAll(t *testing.T) { key := []byte("d") - if ok, err := redis.String(c.Do("hmset", key, 1, 1, 2, 2, 3, 3)); err != nil { + if ok, err := ledis.String(c.Do("hmset", key, 1, 1, 2, 2, 3, 3)); err != nil { t.Fatal(err) } else if ok != OK { t.Fatal(ok) } - if v, err := redis.MultiBulk(c.Do("hgetall", key)); err != nil { + if v, err := ledis.MultiBulk(c.Do("hgetall", key)); err != nil { t.Fatal(err) } else { if err := testHashArray(v, 1, 1, 2, 2, 3, 3); err != nil { @@ -188,7 +188,7 @@ func TestHashGetAll(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("hkeys", key)); err != nil { + if v, err := ledis.MultiBulk(c.Do("hkeys", key)); err != nil { t.Fatal(err) } else { if err := testHashArray(v, 1, 2, 3); err != nil { @@ -196,7 +196,7 @@ func TestHashGetAll(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("hvals", key)); err != nil { + if v, err := ledis.MultiBulk(c.Do("hvals", key)); err != nil { t.Fatal(err) } else { if err := testHashArray(v, 1, 2, 3); err != nil { @@ -204,13 +204,13 @@ func TestHashGetAll(t *testing.T) { } } - if n, err := redis.Int(c.Do("hclear", key)); err != nil { + if n, err := ledis.Int(c.Do("hclear", key)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("hlen", key)); err != nil { + if n, err := ledis.Int(c.Do("hlen", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) diff --git a/server/cmd_kv_test.go b/server/cmd_kv_test.go index 91e0126..4f3cf23 100644 --- a/server/cmd_kv_test.go +++ b/server/cmd_kv_test.go @@ -1,7 +1,7 @@ package server import ( - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "testing" ) @@ -9,65 +9,65 @@ func TestKV(t *testing.T) { c := getTestConn() defer c.Close() - if ok, err := redis.String(c.Do("set", "a", "1234")); err != nil { + if ok, err := ledis.String(c.Do("set", "a", "1234")); err != nil { t.Fatal(err) } else if ok != OK { t.Fatal(ok) } - if n, err := redis.Int(c.Do("setnx", "a", "123")); err != nil { + if n, err := ledis.Int(c.Do("setnx", "a", "123")); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("setnx", "b", "123")); err != nil { + if n, err := ledis.Int(c.Do("setnx", "b", "123")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if v, err := redis.String(c.Do("get", "a")); err != nil { + if v, err := ledis.String(c.Do("get", "a")); err != nil { t.Fatal(err) } else if v != "1234" { t.Fatal(v) } - if v, err := redis.String(c.Do("getset", "a", "123")); err != nil { + if v, err := ledis.String(c.Do("getset", "a", "123")); err != nil { t.Fatal(err) } else if v != "1234" { t.Fatal(v) } - if v, err := redis.String(c.Do("get", "a")); err != nil { + if v, err := ledis.String(c.Do("get", "a")); err != nil { t.Fatal(err) } else if v != "123" { t.Fatal(v) } - if n, err := redis.Int(c.Do("exists", "a")); err != nil { + if n, err := ledis.Int(c.Do("exists", "a")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("exists", "empty_key_test")); err != nil { + if n, err := ledis.Int(c.Do("exists", "empty_key_test")); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if _, err := redis.Int(c.Do("del", "a", "b")); err != nil { + if _, err := ledis.Int(c.Do("del", "a", "b")); err != nil { t.Fatal(err) } - if n, err := redis.Int(c.Do("exists", "a")); err != nil { + if n, err := ledis.Int(c.Do("exists", "a")); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("exists", "b")); err != nil { + if n, err := ledis.Int(c.Do("exists", "b")); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) @@ -78,13 +78,13 @@ func TestKVM(t *testing.T) { c := getTestConn() defer c.Close() - if ok, err := redis.String(c.Do("mset", "a", "1", "b", "2")); err != nil { + if ok, err := ledis.String(c.Do("mset", "a", "1", "b", "2")); err != nil { t.Fatal(err) } else if ok != OK { t.Fatal(ok) } - if v, err := redis.MultiBulk(c.Do("mget", "a", "b", "c")); err != nil { + if v, err := ledis.MultiBulk(c.Do("mget", "a", "b", "c")); err != nil { t.Fatal(err) } else if len(v) != 3 { t.Fatal(len(v)) @@ -107,31 +107,31 @@ func TestKVIncrDecr(t *testing.T) { c := getTestConn() defer c.Close() - if n, err := redis.Int64(c.Do("incr", "n")); err != nil { + if n, err := ledis.Int64(c.Do("incr", "n")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int64(c.Do("incr", "n")); err != nil { + if n, err := ledis.Int64(c.Do("incr", "n")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int64(c.Do("decr", "n")); err != nil { + if n, err := ledis.Int64(c.Do("decr", "n")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int64(c.Do("incrby", "n", 10)); err != nil { + if n, err := ledis.Int64(c.Do("incrby", "n", 10)); err != nil { t.Fatal(err) } else if n != 11 { t.Fatal(n) } - if n, err := redis.Int64(c.Do("decrby", "n", 10)); err != nil { + if n, err := ledis.Int64(c.Do("decrby", "n", 10)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) diff --git a/server/cmd_list_test.go b/server/cmd_list_test.go index c970052..a319f7b 100644 --- a/server/cmd_list_test.go +++ b/server/cmd_list_test.go @@ -2,7 +2,7 @@ package server import ( "fmt" - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "strconv" "testing" ) @@ -11,10 +11,10 @@ func testListIndex(key []byte, index int64, v int) error { c := getTestConn() defer c.Close() - n, err := redis.Int(c.Do("lindex", key, index)) - if err == redis.ErrNil && v != 0 { + n, err := ledis.Int(c.Do("lindex", key, index)) + if err == ledis.ErrNil && v != 0 { return fmt.Errorf("must nil") - } else if err != nil && err != redis.ErrNil { + } else if err != nil && err != ledis.ErrNil { return err } else if n != v { return fmt.Errorf("index err number %d != %d", n, v) @@ -27,7 +27,7 @@ func testListRange(key []byte, start int64, stop int64, checkValues ...int) erro c := getTestConn() defer c.Close() - vs, err := redis.MultiBulk(c.Do("lrange", key, start, stop)) + vs, err := ledis.MultiBulk(c.Do("lrange", key, start, stop)) if err != nil { return err } @@ -59,31 +59,31 @@ func TestList(t *testing.T) { key := []byte("a") - if n, err := redis.Int(c.Do("lpush", key, 1)); err != nil { + if n, err := ledis.Int(c.Do("lpush", key, 1)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("rpush", key, 2)); err != nil { + if n, err := ledis.Int(c.Do("rpush", key, 2)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("rpush", key, 3)); err != nil { + if n, err := ledis.Int(c.Do("rpush", key, 3)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("llen", key)); err != nil { + if n, err := ledis.Int(c.Do("llen", key)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - //for redis-cli a 1 2 3 + //for ledis-cli a 1 2 3 // 127.0.0.1:6379> lrange a 0 0 // 1) "1" if err := testListRange(key, 0, 0, 1); err != nil { @@ -201,7 +201,7 @@ func TestListMPush(t *testing.T) { defer c.Close() key := []byte("b") - if n, err := redis.Int(c.Do("rpush", key, 1, 2, 3)); err != nil { + if n, err := ledis.Int(c.Do("rpush", key, 1, 2, 3)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) @@ -211,7 +211,7 @@ func TestListMPush(t *testing.T) { t.Fatal(err) } - if n, err := redis.Int(c.Do("lpush", key, 1, 2, 3)); err != nil { + if n, err := ledis.Int(c.Do("lpush", key, 1, 2, 3)); err != nil { t.Fatal(err) } else if n != 6 { t.Fatal(n) @@ -227,25 +227,25 @@ func TestPop(t *testing.T) { defer c.Close() key := []byte("c") - if n, err := redis.Int(c.Do("rpush", key, 1, 2, 3, 4, 5, 6)); err != nil { + if n, err := ledis.Int(c.Do("rpush", key, 1, 2, 3, 4, 5, 6)); err != nil { t.Fatal(err) } else if n != 6 { t.Fatal(n) } - if v, err := redis.Int(c.Do("lpop", key)); err != nil { + if v, err := ledis.Int(c.Do("lpop", key)); err != nil { t.Fatal(err) } else if v != 1 { t.Fatal(v) } - if v, err := redis.Int(c.Do("rpop", key)); err != nil { + if v, err := ledis.Int(c.Do("rpop", key)); err != nil { t.Fatal(err) } else if v != 6 { t.Fatal(v) } - if n, err := redis.Int(c.Do("lpush", key, 1)); err != nil { + if n, err := ledis.Int(c.Do("lpush", key, 1)); err != nil { t.Fatal(err) } else if n != 5 { t.Fatal(n) @@ -256,14 +256,14 @@ func TestPop(t *testing.T) { } for i := 1; i <= 5; i++ { - if v, err := redis.Int(c.Do("lpop", key)); err != nil { + if v, err := ledis.Int(c.Do("lpop", key)); err != nil { t.Fatal(err) } else if v != i { t.Fatal(v) } } - if n, err := redis.Int(c.Do("llen", key)); err != nil { + if n, err := ledis.Int(c.Do("llen", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) @@ -271,13 +271,13 @@ func TestPop(t *testing.T) { c.Do("rpush", key, 1, 2, 3, 4, 5) - if n, err := redis.Int(c.Do("lclear", key)); err != nil { + if n, err := ledis.Int(c.Do("lclear", key)); err != nil { t.Fatal(err) } else if n != 5 { t.Fatal(n) } - if n, err := redis.Int(c.Do("llen", key)); err != nil { + if n, err := ledis.Int(c.Do("llen", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) diff --git a/server/cmd_ttl_test.go b/server/cmd_ttl_test.go index 7a1f1f4..c312ba7 100644 --- a/server/cmd_ttl_test.go +++ b/server/cmd_ttl_test.go @@ -1,7 +1,7 @@ package server import ( - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "testing" "time" ) @@ -19,13 +19,13 @@ func TestKVExpire(t *testing.T) { // expire + ttl exp := int64(10) - if n, err := redis.Int(c.Do("expire", k, exp)); err != nil { + if n, err := ledis.Int(c.Do("expire", k, exp)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if ttl, err := redis.Int64(c.Do("ttl", k)); err != nil { + if ttl, err := ledis.Int64(c.Do("ttl", k)); err != nil { t.Fatal(err) } else if ttl != exp { t.Fatal(ttl) @@ -33,13 +33,13 @@ func TestKVExpire(t *testing.T) { // expireat + ttl tm := now() + 3 - if n, err := redis.Int(c.Do("expireat", k, tm)); err != nil { + if n, err := ledis.Int(c.Do("expireat", k, tm)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if ttl, err := redis.Int64(c.Do("ttl", k)); err != nil { + if ttl, err := ledis.Int64(c.Do("ttl", k)); err != nil { t.Fatal(err) } else if ttl != 3 { t.Fatal(ttl) @@ -48,15 +48,15 @@ func TestKVExpire(t *testing.T) { kErr := "not_exist_ttl" // err - expire, expireat - if n, err := redis.Int(c.Do("expire", kErr, tm)); err != nil || n != 0 { + if n, err := ledis.Int(c.Do("expire", kErr, tm)); err != nil || n != 0 { t.Fatal(false) } - if n, err := redis.Int(c.Do("expireat", kErr, tm)); err != nil || n != 0 { + if n, err := ledis.Int(c.Do("expireat", kErr, tm)); err != nil || n != 0 { t.Fatal(false) } - if n, err := redis.Int(c.Do("ttl", kErr)); err != nil || n != -1 { + if n, err := ledis.Int(c.Do("ttl", kErr)); err != nil || n != -1 { t.Fatal(false) } diff --git a/server/cmd_zset.go b/server/cmd_zset.go index dfc24f8..4004134 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -377,7 +377,7 @@ func zrangebyscoreGeneric(c *client, reverse bool) error { } if offset < 0 { - //for redis, if offset < 0, a empty will return + //for ledis, if offset < 0, a empty will return //so here we directly return a empty array c.writeArray([]interface{}{}) return nil diff --git a/server/cmd_zset_test.go b/server/cmd_zset_test.go index fc5b9e0..69664cf 100644 --- a/server/cmd_zset_test.go +++ b/server/cmd_zset_test.go @@ -2,7 +2,7 @@ package server import ( "fmt" - "github.com/siddontang/ledisdb/client/go/redis" + "github.com/siddontang/ledisdb/client/go/ledis" "strconv" "testing" ) @@ -12,91 +12,91 @@ func TestZSet(t *testing.T) { defer c.Close() key := []byte("myzset") - if n, err := redis.Int(c.Do("zadd", key, 3, "a", 4, "b")); err != nil { + if n, err := ledis.Int(c.Do("zadd", key, 3, "a", 4, "b")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(n) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zadd", key, 1, "a", 2, "b")); err != nil { + if n, err := ledis.Int(c.Do("zadd", key, 1, "a", 2, "b")); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(n) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zadd", key, 3, "c", 4, "d")); err != nil { + if n, err := ledis.Int(c.Do("zadd", key, 3, "c", 4, "d")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 4 { t.Fatal(n) } - if s, err := redis.Int(c.Do("zscore", key, "c")); err != nil { + if s, err := ledis.Int(c.Do("zscore", key, "c")); err != nil { t.Fatal(err) } else if s != 3 { t.Fatal(s) } - if n, err := redis.Int(c.Do("zrem", key, "d", "e")); err != nil { + if n, err := ledis.Int(c.Do("zrem", key, "d", "e")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zincrby", key, 4, "c")); err != nil { + if n, err := ledis.Int(c.Do("zincrby", key, 4, "c")); err != nil { t.Fatal(err) } else if n != 7 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zincrby", key, -4, "c")); err != nil { + if n, err := ledis.Int(c.Do("zincrby", key, -4, "c")); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zincrby", key, 4, "d")); err != nil { + if n, err := ledis.Int(c.Do("zincrby", key, 4, "d")); err != nil { t.Fatal(err) } else if n != 4 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 4 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zrem", key, "a", "b", "c", "d")); err != nil { + if n, err := ledis.Int(c.Do("zrem", key, "a", "b", "c", "d")); err != nil { t.Fatal(err) } else if n != 4 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) @@ -109,47 +109,47 @@ func TestZSetCount(t *testing.T) { defer c.Close() key := []byte("myzset") - if _, err := redis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { + if _, err := ledis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { t.Fatal(err) } - if n, err := redis.Int(c.Do("zcount", key, 2, 4)); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, 2, 4)); err != nil { t.Fatal(err) } else if n != 3 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, 4, 4)); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, 4, 4)); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, 4, 3)); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, 4, 3)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, "(2", 4)); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, "(2", 4)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, "2", "(4")); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, "2", "(4")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, "(2", "(4")); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, "(2", "(4")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcount", key, "-inf", "+inf")); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, "-inf", "+inf")); err != nil { t.Fatal(err) } else if n != 4 { t.Fatal(n) @@ -157,7 +157,7 @@ func TestZSetCount(t *testing.T) { c.Do("zadd", key, 3, "e") - if n, err := redis.Int(c.Do("zcount", key, "(2", "(4")); err != nil { + if n, err := ledis.Int(c.Do("zcount", key, "(2", "(4")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) @@ -171,27 +171,27 @@ func TestZSetRank(t *testing.T) { defer c.Close() key := []byte("myzset") - if _, err := redis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { + if _, err := ledis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { t.Fatal(err) } - if n, err := redis.Int(c.Do("zrank", key, "c")); err != nil { + if n, err := ledis.Int(c.Do("zrank", key, "c")); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if _, err := redis.Int(c.Do("zrank", key, "e")); err != redis.ErrNil { + if _, err := ledis.Int(c.Do("zrank", key, "e")); err != ledis.ErrNil { t.Fatal(err) } - if n, err := redis.Int(c.Do("zrevrank", key, "c")); err != nil { + if n, err := ledis.Int(c.Do("zrevrank", key, "c")); err != nil { t.Fatal(err) } else if n != 1 { t.Fatal(n) } - if _, err := redis.Int(c.Do("zrevrank", key, "e")); err != redis.ErrNil { + if _, err := ledis.Int(c.Do("zrevrank", key, "e")); err != ledis.ErrNil { t.Fatal(err) } } @@ -228,11 +228,11 @@ func TestZSetRangeScore(t *testing.T) { defer c.Close() key := []byte("myzset_range") - if _, err := redis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { + if _, err := ledis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { t.Fatal(err) } - if v, err := redis.MultiBulk(c.Do("zrangebyscore", key, 1, 4, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrangebyscore", key, 1, 4, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", 1, "b", 2, "c", 3, "d", 4); err != nil { @@ -240,7 +240,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrangebyscore", key, 1, 4, "withscores", "limit", 1, 2)); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrangebyscore", key, 1, 4, "withscores", "limit", 1, 2)); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "b", 2, "c", 3); err != nil { @@ -248,7 +248,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrangebyscore", key, "-inf", "+inf", "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrangebyscore", key, "-inf", "+inf", "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", 1, "b", 2, "c", 3, "d", 4); err != nil { @@ -256,7 +256,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrangebyscore", key, "(1", "(4")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrangebyscore", key, "(1", "(4")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "b", "c"); err != nil { @@ -264,7 +264,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrangebyscore", key, 1, 4, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrangebyscore", key, 1, 4, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "d", 4, "c", 3, "b", 2, "a", 1); err != nil { @@ -272,7 +272,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrangebyscore", key, 1, 4, "withscores", "limit", 1, 2)); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrangebyscore", key, 1, 4, "withscores", "limit", 1, 2)); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "c", 3, "b", 2); err != nil { @@ -280,7 +280,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrangebyscore", key, "-inf", "+inf", "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrangebyscore", key, "-inf", "+inf", "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "d", 4, "c", 3, "b", 2, "a", 1); err != nil { @@ -288,7 +288,7 @@ func TestZSetRangeScore(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrangebyscore", key, "(1", "(4")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrangebyscore", key, "(1", "(4")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "c", "b"); err != nil { @@ -296,19 +296,19 @@ func TestZSetRangeScore(t *testing.T) { } } - if n, err := redis.Int(c.Do("zremrangebyscore", key, 2, 3)); err != nil { + if n, err := ledis.Int(c.Do("zremrangebyscore", key, 2, 3)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if v, err := redis.MultiBulk(c.Do("zrangebyscore", key, 1, 4)); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrangebyscore", key, 1, 4)); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", "d"); err != nil { @@ -322,11 +322,11 @@ func TestZSetRange(t *testing.T) { defer c.Close() key := []byte("myzset_range_rank") - if _, err := redis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { + if _, err := ledis.Int(c.Do("zadd", key, 1, "a", 2, "b", 3, "c", 4, "d")); err != nil { t.Fatal(err) } - if v, err := redis.MultiBulk(c.Do("zrange", key, 0, 3, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, 0, 3, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", 1, "b", 2, "c", 3, "d", 4); err != nil { @@ -334,7 +334,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrange", key, 1, 4, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, 1, 4, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "b", 2, "c", 3, "d", 4); err != nil { @@ -342,7 +342,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrange", key, -2, -1, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, -2, -1, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "c", 3, "d", 4); err != nil { @@ -350,7 +350,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrange", key, 0, -1, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, 0, -1, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", 1, "b", 2, "c", 3, "d", 4); err != nil { @@ -358,13 +358,13 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrange", key, -1, -2, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, -1, -2, "withscores")); err != nil { t.Fatal(err) } else if len(v) != 0 { t.Fatal(len(v)) } - if v, err := redis.MultiBulk(c.Do("zrevrange", key, 0, 4, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrange", key, 0, 4, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "d", 4, "c", 3, "b", 2, "a", 1); err != nil { @@ -372,7 +372,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrange", key, 0, -1, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrange", key, 0, -1, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "d", 4, "c", 3, "b", 2, "a", 1); err != nil { @@ -380,7 +380,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrange", key, 2, 3, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrange", key, 2, 3, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "b", 2, "a", 1); err != nil { @@ -388,7 +388,7 @@ func TestZSetRange(t *testing.T) { } } - if v, err := redis.MultiBulk(c.Do("zrevrange", key, -2, -1, "withscores")); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrevrange", key, -2, -1, "withscores")); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "b", 2, "a", 1); err != nil { @@ -396,19 +396,19 @@ func TestZSetRange(t *testing.T) { } } - if n, err := redis.Int(c.Do("zremrangebyrank", key, 2, 3)); err != nil { + if n, err := ledis.Int(c.Do("zremrangebyrank", key, 2, 3)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if v, err := redis.MultiBulk(c.Do("zrange", key, 0, 4)); err != nil { + if v, err := ledis.MultiBulk(c.Do("zrange", key, 0, 4)); err != nil { t.Fatal(err) } else { if err := testZSetRange(v, "a", "b"); err != nil { @@ -416,13 +416,13 @@ func TestZSetRange(t *testing.T) { } } - if n, err := redis.Int(c.Do("zclear", key)); err != nil { + if n, err := ledis.Int(c.Do("zclear", key)); err != nil { t.Fatal(err) } else if n != 2 { t.Fatal(n) } - if n, err := redis.Int(c.Do("zcard", key)); err != nil { + if n, err := ledis.Int(c.Do("zcard", key)); err != nil { t.Fatal(err) } else if n != 0 { t.Fatal(n) From cdb78498afdaa5dacef0c419104187dc048282e0 Mon Sep 17 00:00:00 2001 From: siddontang Date: Sun, 22 Jun 2014 15:25:51 +0800 Subject: [PATCH 4/4] ledis use proper return values not []interface{} This is a very big change and not backward compatible --- ledis/t_hash.go | 33 ++++++++++++++---------- ledis/t_hash_test.go | 4 +-- ledis/t_kv.go | 4 +-- ledis/t_kv_test.go | 4 +-- ledis/t_list.go | 8 +++--- ledis/t_zset.go | 61 +++++++++++++++----------------------------- ledis/t_zset_test.go | 14 +++++----- server/client.go | 56 ++++++++++++++++++++++++++++++++++++++++ server/cmd_hash.go | 8 +++--- server/cmd_kv.go | 2 +- server/cmd_list.go | 2 +- server/cmd_zset.go | 20 +++------------ 12 files changed, 121 insertions(+), 95 deletions(-) diff --git a/ledis/t_hash.go b/ledis/t_hash.go index 5186c04..1478a21 100644 --- a/ledis/t_hash.go +++ b/ledis/t_hash.go @@ -232,13 +232,13 @@ func (db *DB) HMset(key []byte, args ...FVPair) error { return err } -func (db *DB) HMget(key []byte, args ...[]byte) ([]interface{}, error) { +func (db *DB) HMget(key []byte, args ...[]byte) ([][]byte, error) { var ek []byte it := db.db.NewIterator() defer it.Close() - r := make([]interface{}, len(args)) + r := make([][]byte, len(args)) for i := 0; i < len(args); i++ { if err := checkHashKFSize(key, args[i]); err != nil { return nil, err @@ -344,7 +344,7 @@ func (db *DB) HIncrBy(key []byte, field []byte, delta int64) (int64, error) { return n, err } -func (db *DB) HGetAll(key []byte) ([]interface{}, error) { +func (db *DB) HGetAll(key []byte) ([]FVPair, error) { if err := checkKeySize(key); err != nil { return nil, err } @@ -352,16 +352,16 @@ func (db *DB) HGetAll(key []byte) ([]interface{}, error) { start := db.hEncodeStartKey(key) stop := db.hEncodeStopKey(key) - v := make([]interface{}, 0, 16) + v := make([]FVPair, 0, 16) it := db.db.RangeLimitIterator(start, stop, leveldb.RangeROpen, 0, -1) for ; it.Valid(); it.Next() { - _, k, err := db.hDecodeHashKey(it.Key()) + _, f, err := db.hDecodeHashKey(it.Key()) if err != nil { return nil, err } - v = append(v, k) - v = append(v, it.Value()) + + v = append(v, FVPair{Field: f, Value: it.Value()}) } it.Close() @@ -369,7 +369,7 @@ func (db *DB) HGetAll(key []byte) ([]interface{}, error) { return v, nil } -func (db *DB) HKeys(key []byte) ([]interface{}, error) { +func (db *DB) HKeys(key []byte) ([][]byte, error) { if err := checkKeySize(key); err != nil { return nil, err } @@ -377,15 +377,15 @@ func (db *DB) HKeys(key []byte) ([]interface{}, error) { start := db.hEncodeStartKey(key) stop := db.hEncodeStopKey(key) - v := make([]interface{}, 0, 16) + v := make([][]byte, 0, 16) it := db.db.RangeLimitIterator(start, stop, leveldb.RangeROpen, 0, -1) for ; it.Valid(); it.Next() { - _, k, err := db.hDecodeHashKey(it.Key()) + _, f, err := db.hDecodeHashKey(it.Key()) if err != nil { return nil, err } - v = append(v, k) + v = append(v, f) } it.Close() @@ -393,7 +393,7 @@ func (db *DB) HKeys(key []byte) ([]interface{}, error) { return v, nil } -func (db *DB) HValues(key []byte) ([]interface{}, error) { +func (db *DB) HValues(key []byte) ([][]byte, error) { if err := checkKeySize(key); err != nil { return nil, err } @@ -401,10 +401,15 @@ func (db *DB) HValues(key []byte) ([]interface{}, error) { start := db.hEncodeStartKey(key) stop := db.hEncodeStopKey(key) - v := make([]interface{}, 0, 16) + v := make([][]byte, 0, 16) it := db.db.RangeLimitIterator(start, stop, leveldb.RangeROpen, 0, -1) for ; it.Valid(); it.Next() { + _, _, err := db.hDecodeHashKey(it.Key()) + if err != nil { + return nil, err + } + v = append(v, it.Value()) } @@ -477,7 +482,7 @@ func (db *DB) HScan(key []byte, field []byte, count int, inclusive bool) ([]FVPa count = defaultScanCount } - v := make([]FVPair, 0, 2*count) + v := make([]FVPair, 0, count) rangeType := leveldb.RangeROpen if !inclusive { diff --git a/ledis/t_hash_test.go b/ledis/t_hash_test.go index a663c2e..b18a616 100644 --- a/ledis/t_hash_test.go +++ b/ledis/t_hash_test.go @@ -46,11 +46,11 @@ func TestDBHash(t *testing.T) { ay, _ := db.HMget(key, []byte("a"), []byte("b")) - if v1, _ := ay[0].([]byte); string(v1) != "hello world 1" { + if v1 := ay[0]; string(v1) != "hello world 1" { t.Fatal(string(v1)) } - if v2, _ := ay[1].([]byte); string(v2) != "hello world 2" { + if v2 := ay[1]; string(v2) != "hello world 2" { t.Fatal(string(v2)) } diff --git a/ledis/t_kv.go b/ledis/t_kv.go index 3008fb8..5d4c4fc 100644 --- a/ledis/t_kv.go +++ b/ledis/t_kv.go @@ -201,8 +201,8 @@ func (db *DB) IncryBy(key []byte, increment int64) (int64, error) { return db.incr(key, increment) } -func (db *DB) MGet(keys ...[]byte) ([]interface{}, error) { - values := make([]interface{}, len(keys)) +func (db *DB) MGet(keys ...[]byte) ([][]byte, error) { + values := make([][]byte, len(keys)) it := db.db.NewIterator() defer it.Close() diff --git a/ledis/t_kv_test.go b/ledis/t_kv_test.go index 9088bd1..6c5e7e0 100644 --- a/ledis/t_kv_test.go +++ b/ledis/t_kv_test.go @@ -33,11 +33,11 @@ func TestDBKV(t *testing.T) { ay, _ := db.MGet(key1, key2) - if v1, _ := ay[0].([]byte); string(v1) != "hello world 1" { + if v1 := ay[0]; string(v1) != "hello world 1" { t.Fatal(string(v1)) } - if v2, _ := ay[1].([]byte); string(v2) != "hello world 2" { + if v2 := ay[1]; string(v2) != "hello world 2" { t.Fatal(string(v2)) } diff --git a/ledis/t_list.go b/ledis/t_list.go index e5d2f42..1d3c91e 100644 --- a/ledis/t_list.go +++ b/ledis/t_list.go @@ -317,20 +317,20 @@ func (db *DB) LPush(key []byte, args ...[]byte) (int64, error) { return db.lpush(key, listHeadSeq, args...) } -func (db *DB) LRange(key []byte, start int32, stop int32) ([]interface{}, error) { +func (db *DB) LRange(key []byte, start int32, stop int32) ([][]byte, error) { if err := checkKeySize(key); err != nil { return nil, err } - v := make([]interface{}, 0, 16) - var startSeq int32 var stopSeq int32 if start > stop { - return []interface{}{}, nil + return [][]byte{}, nil } + v := make([][]byte, 0, 16) + var headSeq int32 var tailSeq int32 var err error diff --git a/ledis/t_zset.go b/ledis/t_zset.go index a4ea355..681996b 100644 --- a/ledis/t_zset.go +++ b/ledis/t_zset.go @@ -534,38 +534,21 @@ func (db *DB) zRemRange(t *tx, key []byte, min int64, max int64, offset int, lim return num, nil } -func (db *DB) zReverse(s []interface{}, withScores bool) []interface{} { - if withScores { - for i, j := 0, len(s)-2; i < j; i, j = i+2, j-2 { - s[i], s[j] = s[j], s[i] - s[i+1], s[j+1] = s[j+1], s[i+1] - } - } else { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } - } - - return s -} - -func (db *DB) zRange(key []byte, min int64, max int64, withScores bool, offset int, limit int, reverse bool) ([]interface{}, error) { +func (db *DB) zRange(key []byte, min int64, max int64, offset int, limit int, reverse bool) ([]ScorePair, error) { if len(key) > MaxKeySize { return nil, errKeySize } if offset < 0 { - return []interface{}{}, nil + return []ScorePair{}, nil } nv := 64 if limit > 0 { nv = limit } - if withScores { - nv = 2 * nv - } - v := make([]interface{}, 0, nv) + + v := make([]ScorePair, 0, nv) var it *leveldb.RangeLimitIterator @@ -584,16 +567,14 @@ func (db *DB) zRange(key []byte, min int64, max int64, withScores bool, offset i continue } - if withScores { - v = append(v, m, s) - } else { - v = append(v, m) - } + v = append(v, ScorePair{Member: m, Score: s}) } it.Close() if reverse && (offset == 0 && limit < 0) { - v = db.zReverse(v, withScores) + for i, j := 0, len(v)-1; i < j; i, j = i+1, j-1 { + v[i], v[j] = v[j], v[i] + } } return v, nil @@ -650,15 +631,15 @@ func (db *DB) ZClear(key []byte) (int64, error) { return rmCnt, err } -func (db *DB) ZRange(key []byte, start int, stop int, withScores bool) ([]interface{}, error) { - return db.ZRangeGeneric(key, start, stop, withScores, false) +func (db *DB) ZRange(key []byte, start int, stop int) ([]ScorePair, error) { + return db.ZRangeGeneric(key, start, stop, false) } //min and max must be inclusive //if no limit, set offset = 0 and count = -1 func (db *DB) ZRangeByScore(key []byte, min int64, max int64, - withScores bool, offset int, count int) ([]interface{}, error) { - return db.ZRangeByScoreGeneric(key, min, max, withScores, offset, count, false) + offset int, count int) ([]ScorePair, error) { + return db.ZRangeByScoreGeneric(key, min, max, offset, count, false) } func (db *DB) ZRank(key []byte, member []byte) (int64, error) { @@ -699,8 +680,8 @@ func (db *DB) ZRemRangeByScore(key []byte, min int64, max int64) (int64, error) return rmCnt, err } -func (db *DB) ZRevRange(key []byte, start int, stop int, withScores bool) ([]interface{}, error) { - return db.ZRangeGeneric(key, start, stop, withScores, true) +func (db *DB) ZRevRange(key []byte, start int, stop int) ([]ScorePair, error) { + return db.ZRangeGeneric(key, start, stop, true) } func (db *DB) ZRevRank(key []byte, member []byte) (int64, error) { @@ -709,27 +690,25 @@ func (db *DB) ZRevRank(key []byte, member []byte) (int64, error) { //min and max must be inclusive //if no limit, set offset = 0 and count = -1 -func (db *DB) ZRevRangeByScore(key []byte, min int64, max int64, - withScores bool, offset int, count int) ([]interface{}, error) { - return db.ZRangeByScoreGeneric(key, min, max, withScores, offset, count, true) +func (db *DB) ZRevRangeByScore(key []byte, min int64, max int64, offset int, count int) ([]ScorePair, error) { + return db.ZRangeByScoreGeneric(key, min, max, offset, count, true) } -func (db *DB) ZRangeGeneric(key []byte, start int, stop int, - withScores bool, reverse bool) ([]interface{}, error) { +func (db *DB) ZRangeGeneric(key []byte, start int, stop int, reverse bool) ([]ScorePair, error) { offset, limit, err := db.zParseLimit(key, start, stop) if err != nil { return nil, err } - return db.zRange(key, MinScore, MaxScore, withScores, offset, limit, reverse) + return db.zRange(key, MinScore, MaxScore, offset, limit, reverse) } //min and max must be inclusive //if no limit, set offset = 0 and count = -1 func (db *DB) ZRangeByScoreGeneric(key []byte, min int64, max int64, - withScores bool, offset int, count int, reverse bool) ([]interface{}, error) { + offset int, count int, reverse bool) ([]ScorePair, error) { - return db.zRange(key, min, max, withScores, offset, count, reverse) + return db.zRange(key, min, max, offset, count, reverse) } func (db *DB) zFlush() (drop int64, err error) { diff --git a/ledis/t_zset_test.go b/ledis/t_zset_test.go index 2b637e6..4b08136 100644 --- a/ledis/t_zset_test.go +++ b/ledis/t_zset_test.go @@ -145,15 +145,13 @@ func TestZSetOrder(t *testing.T) { } } - if qMembs, err := db.ZRange(key, 0, endPos, false); err != nil { + if qMembs, err := db.ZRange(key, 0, endPos); err != nil { t.Fatal(err) } else if len(qMembs) != membCnt { t.Fatal(fmt.Sprintf("%d vs %d", len(qMembs), membCnt)) } else { for i := 0; i < membCnt; i++ { - if memb, ok := qMembs[i].([]byte); !ok { - t.Fatal(ok) - } else if string(memb) != membs[i] { + if string(qMembs[i].Member) != membs[i] { t.Fatal(fmt.Sprintf("[%s] vs [%s]", qMembs[i], membs[i])) } } @@ -182,7 +180,7 @@ func TestZSetOrder(t *testing.T) { t.Fatal(pos) } - if qMembs, err := db.ZRangeByScore(key, 999, 0XFFFF, false, 0, membCnt); err != nil { + if qMembs, err := db.ZRangeByScore(key, 999, 0XFFFF, 0, membCnt); err != nil { t.Fatal(err) } else if len(qMembs) != 1 { t.Fatal(len(qMembs)) @@ -203,12 +201,12 @@ func TestZSetOrder(t *testing.T) { t.Fatal(pos) } - if datas, _ := db.ZRange(key, 0, endPos, true); len(datas) != 12 { + if datas, _ := db.ZRange(key, 0, endPos); len(datas) != 6 { t.Fatal(len(datas)) } else { scores := []int64{0, 1, 2, 5, 6, 999} - for i := 1; i < len(datas); i += 2 { - if s, ok := datas[i].(int64); !ok || s != scores[(i-1)/2] { + for i := 0; i < len(datas); i++ { + if datas[i].Score != scores[i] { t.Fatal(fmt.Sprintf("[%d]=%d", i, datas[i])) } } diff --git a/server/client.go b/server/client.go index 21412a3..e2d6865 100644 --- a/server/client.go +++ b/server/client.go @@ -245,6 +245,62 @@ func (c *client) writeArray(ay []interface{}) { } } +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))) diff --git a/server/cmd_hash.go b/server/cmd_hash.go index 23873b1..1dd36e0 100644 --- a/server/cmd_hash.go +++ b/server/cmd_hash.go @@ -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.writeArray(v) + c.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.writeArray(v) + c.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.writeArray(v) + c.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.writeArray(v) + c.writeSliceArray(v) } return nil diff --git a/server/cmd_kv.go b/server/cmd_kv.go index f70a508..d924e4e 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -197,7 +197,7 @@ func mgetCommand(c *client) error { if v, err := c.db.MGet(args...); err != nil { return err } else { - c.writeArray(v) + c.writeSliceArray(v) } return nil diff --git a/server/cmd_list.go b/server/cmd_list.go index 58d64f3..0992f56 100644 --- a/server/cmd_list.go +++ b/server/cmd_list.go @@ -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.writeArray(v) + c.writeSliceArray(v) } return nil diff --git a/server/cmd_zset.go b/server/cmd_zset.go index 4004134..4411113 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -312,16 +312,10 @@ func zrangeGeneric(c *client, reverse bool) error { withScores = true } - if datas, err := c.db.ZRangeGeneric(key, start, stop, withScores, reverse); err != nil { + if datas, err := c.db.ZRangeGeneric(key, start, stop, reverse); err != nil { return err } else { - if withScores { - for i := len(datas) - 1; i > 0; i -= 2 { - v, _ := datas[i].(int64) - datas[i] = ledis.StrPutInt64(v) - } - } - c.writeArray(datas) + c.writeScorePairArray(datas, withScores) } return nil } @@ -383,16 +377,10 @@ func zrangebyscoreGeneric(c *client, reverse bool) error { return nil } - if datas, err := c.db.ZRangeByScoreGeneric(key, min, max, withScores, offset, count, reverse); err != nil { + if datas, err := c.db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil { return err } else { - if withScores { - for i := len(datas) - 1; i > 0; i -= 2 { - v, _ := datas[i].(int64) - datas[i] = ledis.StrPutInt64(v) - } - } - c.writeArray(datas) + c.writeScorePairArray(datas, withScores) } return nil