server add scan support

This commit is contained in:
siddontang 2014-08-26 23:21:45 +08:00
parent 059024c6ac
commit cb5a61240d
10 changed files with 281 additions and 1 deletions

View File

@ -20,7 +20,7 @@ func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match s
} }
} }
if key != nil { if len(key) > 0 {
if err = checkKeySize(key); err != nil { if err = checkKeySize(key); err != nil {
return nil, err return nil, err
} }

View File

@ -203,6 +203,8 @@ func (w *respWriter) writeArray(lst []interface{}) {
switch v := lst[i].(type) { switch v := lst[i].(type) {
case []interface{}: case []interface{}:
w.writeArray(v) w.writeArray(v)
case [][]byte:
w.writeSliceArray(v)
case []byte: case []byte:
w.writeBulk(v) w.writeBulk(v)
case nil: case nil:

View File

@ -272,6 +272,27 @@ func bpersistCommand(c *client) error {
return nil return nil
} }
func bscanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.BScan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = ""
} else {
data[0] = append([]byte{'('}, ay[len(ay)-1]...)
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("bget", bgetCommand) register("bget", bgetCommand)
register("bdelete", bdeleteCommand) register("bdelete", bdeleteCommand)
@ -284,4 +305,5 @@ func init() {
register("bexpireat", bexpireAtCommand) register("bexpireat", bexpireAtCommand)
register("bttl", bttlCommand) register("bttl", bttlCommand)
register("bpersist", bpersistCommand) register("bpersist", bpersistCommand)
register("bscan", bscanCommand)
} }

View File

@ -292,6 +292,27 @@ func hpersistCommand(c *client) error {
return nil return nil
} }
func hscanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.HScan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = ""
} else {
data[0] = append([]byte{'('}, ay[len(ay)-1]...)
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("hdel", hdelCommand) register("hdel", hdelCommand)
register("hexists", hexistsCommand) register("hexists", hexistsCommand)
@ -313,4 +334,5 @@ func init() {
register("hexpireat", hexpireAtCommand) register("hexpireat", hexpireAtCommand)
register("httl", httlCommand) register("httl", httlCommand)
register("hpersist", hpersistCommand) register("hpersist", hpersistCommand)
register("hscan", hscanCommand)
} }

View File

@ -2,6 +2,8 @@ package server
import ( import (
"github.com/siddontang/ledisdb/ledis" "github.com/siddontang/ledisdb/ledis"
"strconv"
"strings"
) )
func getCommand(c *client) error { func getCommand(c *client) error {
@ -273,6 +275,81 @@ func persistCommand(c *client) error {
return nil return nil
} }
func parseScanArgs(c *client) (key []byte, inclusive bool, match string, count int, err error) {
args := c.args
count = 10
inclusive = false
switch len(args) {
case 0:
key = nil
return
case 1, 3, 5:
key = args[0]
break
case 2, 4, 6:
key = args[0]
if strings.ToLower(ledis.String(args[len(args)-1])) != "inclusive" {
err = ErrCmdParams
return
}
inclusive = true
args = args[0 : len(args)-1]
default:
err = ErrCmdParams
return
}
if len(args) == 3 {
switch strings.ToLower(ledis.String(args[1])) {
case "match":
match = ledis.String(args[2])
return
case "count":
count, err = strconv.Atoi(ledis.String(args[2]))
return
default:
err = ErrCmdParams
return
}
} else if len(args) == 5 {
if strings.ToLower(ledis.String(args[1])) != "match" {
err = ErrCmdParams
return
} else if strings.ToLower(ledis.String(args[3])) != "count" {
err = ErrCmdParams
return
}
match = ledis.String(args[2])
count, err = strconv.Atoi(ledis.String(args[4]))
return
}
return
}
func scanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.Scan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = []byte("")
} else {
data[0] = ay[len(ay)-1]
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("decr", decrCommand) register("decr", decrCommand)
register("decrby", decrbyCommand) register("decrby", decrbyCommand)
@ -290,4 +367,5 @@ func init() {
register("expireat", expireAtCommand) register("expireat", expireAtCommand)
register("ttl", ttlCommand) register("ttl", ttlCommand)
register("persist", persistCommand) register("persist", persistCommand)
register("scan", scanCommand)
} }

View File

@ -228,6 +228,27 @@ func lpersistCommand(c *client) error {
return nil return nil
} }
func lscanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.LScan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = ""
} else {
data[0] = append([]byte{'('}, ay[len(ay)-1]...)
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("lindex", lindexCommand) register("lindex", lindexCommand)
register("llen", llenCommand) register("llen", llenCommand)
@ -245,4 +266,5 @@ func init() {
register("lexpireat", lexpireAtCommand) register("lexpireat", lexpireAtCommand)
register("lttl", lttlCommand) register("lttl", lttlCommand)
register("lpersist", lpersistCommand) register("lpersist", lpersistCommand)
register("lscan", lscanCommand)
} }

View File

@ -43,6 +43,7 @@ func TestReplication(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer master.Close()
slaveCfg := new(config.Config) slaveCfg := new(config.Config)
slaveCfg.DataDir = fmt.Sprintf("%s/slave", data_dir) slaveCfg.DataDir = fmt.Sprintf("%s/slave", data_dir)
@ -53,6 +54,7 @@ func TestReplication(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer slave.Close()
go master.Run() go master.Run()

View File

@ -262,6 +262,27 @@ func spersistCommand(c *client) error {
return nil return nil
} }
func sscanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.SScan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = ""
} else {
data[0] = append([]byte{'('}, ay[len(ay)-1]...)
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("sadd", saddCommand) register("sadd", saddCommand)
register("scard", scardCommand) register("scard", scardCommand)
@ -280,4 +301,5 @@ func init() {
register("sexpireat", sexpireAtCommand) register("sexpireat", sexpireAtCommand)
register("sttl", sttlCommand) register("sttl", sttlCommand)
register("spersist", spersistCommand) register("spersist", spersistCommand)
register("sscan", sscanCommand)
} }

View File

@ -638,6 +638,27 @@ func zinterstoreCommand(c *client) error {
return err return err
} }
func zscanCommand(c *client) error {
key, inclusive, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.ZScan(key, count, inclusive, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
if len(ay) < count {
data[0] = ""
} else {
data[0] = append([]byte{'('}, ay[len(ay)-1]...)
}
data[1] = ay
c.resp.writeArray(data)
}
return nil
}
func init() { func init() {
register("zadd", zaddCommand) register("zadd", zaddCommand)
register("zcard", zcardCommand) register("zcard", zcardCommand)
@ -665,4 +686,5 @@ func init() {
register("zexpireat", zexpireAtCommand) register("zexpireat", zexpireAtCommand)
register("zttl", zttlCommand) register("zttl", zttlCommand)
register("zpersist", zpersistCommand) register("zpersist", zpersistCommand)
register("zscan", zscanCommand)
} }

88
server/scan_test.go Normal file
View File

@ -0,0 +1,88 @@
package server
import (
"fmt"
"github.com/siddontang/ledisdb/client/go/ledis"
"github.com/siddontang/ledisdb/config"
"os"
"testing"
)
func TestScan(t *testing.T) {
cfg := new(config.Config)
cfg.DataDir = "/tmp/test_scan"
cfg.Addr = "127.0.0.1:11185"
os.RemoveAll(cfg.DataDir)
s, err := NewApp(cfg)
if err != nil {
t.Fatal(err)
}
go s.Run()
defer s.Close()
cc := new(ledis.Config)
cc.Addr = cfg.Addr
cc.MaxIdleConns = 1
c := ledis.NewClient(cc)
defer c.Close()
testKVScan(t, c)
}
func checkScanValues(t *testing.T, ay interface{}, values ...int) {
a, err := ledis.Strings(ay, nil)
if err != nil {
t.Fatal(err)
}
if len(a) != len(values) {
t.Fatal(fmt.Sprintf("len %d != %d", len(a), len(values)))
}
for i, v := range a {
if string(v) != fmt.Sprintf("%d", values[i]) {
t.Fatal(fmt.Sprintf("%d %s != %d", string(v), values[i]))
}
}
}
func testKVScan(t *testing.T, c *ledis.Client) {
for i := 0; i < 10; i++ {
if _, err := c.Do("set", fmt.Sprintf("%d", i), []byte("value")); err != nil {
t.Fatal(err)
}
}
if ay, err := ledis.Values(c.Do("scan", "", "count", 5)); err != nil {
t.Fatal(err)
} else if len(ay) != 2 {
t.Fatal(len(ay))
} else if n := ay[0].([]byte); string(n) != "4" {
t.Fatal(string(n))
} else {
checkScanValues(t, ay[1], 0, 1, 2, 3, 4)
}
if ay, err := ledis.Values(c.Do("scan", "4", "count", 6)); err != nil {
t.Fatal(err)
} else if len(ay) != 2 {
t.Fatal(len(ay))
} else if n := ay[0].([]byte); string(n) != "" {
t.Fatal(string(n))
} else {
checkScanValues(t, ay[1], 5, 6, 7, 8, 9)
}
if ay, err := ledis.Values(c.Do("scan", "4", "count", 6, "inclusive")); err != nil {
t.Fatal(err)
} else if len(ay) != 2 {
t.Fatal(len(ay))
} else if n := ay[0].([]byte); string(n) != "9" {
t.Fatal(string(n))
} else {
checkScanValues(t, ay[1], 4, 5, 6, 7, 8, 9)
}
}