Merge branch 'develop'

This commit is contained in:
siddontang 2014-06-22 15:28:09 +08:00
commit 9680161559
34 changed files with 798 additions and 2158 deletions

View File

@ -0,0 +1,84 @@
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+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")
}
}

96
client/go/ledis/client.go Normal file
View File

@ -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()
}
}

309
client/go/ledis/conn.go Normal file
View File

@ -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
}

View File

@ -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.

View File

@ -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()
}

View File

@ -1,18 +1,4 @@
// 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
package ledis
import (
"errors"
@ -21,7 +7,7 @@ import (
)
// ErrNil indicates that a reply value is nil.
var ErrNil = errors.New("redigo: nil returned")
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
@ -51,7 +37,7 @@ func Int(reply interface{}, err error) (int, error) {
case Error:
return 0, reply
}
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", 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
@ -78,10 +64,10 @@ func Int64(reply interface{}, err error) (int64, error) {
case Error:
return 0, reply
}
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
return 0, fmt.Errorf("ledis: unexpected type for Int64, got type %T", reply)
}
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
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
@ -110,7 +96,7 @@ func Uint64(reply interface{}, err error) (uint64, error) {
case Error:
return 0, reply
}
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", 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
@ -134,7 +120,7 @@ func Float64(reply interface{}, err error) (float64, error) {
case Error:
return 0, reply
}
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", 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
@ -160,7 +146,7 @@ func String(reply interface{}, err error) (string, error) {
case Error:
return "", reply
}
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", 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
@ -186,7 +172,7 @@ func Bytes(reply interface{}, err error) ([]byte, error) {
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", 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
@ -212,7 +198,7 @@ func Bool(reply interface{}, err error) (bool, error) {
case Error:
return false, reply
}
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
return false, fmt.Errorf("ledis: unexpected type for Bool, got type %T", reply)
}
// MultiBulk is deprecated. Use Values.
@ -238,7 +224,7 @@ func Values(reply interface{}, err error) ([]interface{}, error) {
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", 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
@ -257,7 +243,7 @@ func Strings(reply interface{}, err error) ([]string, error) {
}
p, ok := reply[i].([]byte)
if !ok {
return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
return nil, fmt.Errorf("ledis: unexpected element type for Strings, got type %T", reply[i])
}
result[i] = string(p)
}
@ -267,5 +253,5 @@ func Strings(reply interface{}, err error) ([]string, error) {
case Error:
return nil, reply
}
return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
return nil, fmt.Errorf("ledis: unexpected type for Strings, got type %T", reply)
}

View File

@ -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)]
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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()
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()

View File

@ -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 {

View File

@ -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))
}

View File

@ -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()

View File

@ -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))
}

View File

@ -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

View File

@ -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) {

View File

@ -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]))
}
}

View File

@ -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
func newTestLedisClient() {
cfg := new(ledis.Config)
cfg.Addr = "127.0.0.1:16380"
cfg.MaxIdleConns = 4
testLedisClient = ledis.NewClient(cfg)
}
return c, nil
}
testPool = redis.NewPool(f, 4)
}
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")

View File

@ -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)))

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}
@ -377,22 +371,16 @@ 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
}
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

View File

@ -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)