add rev scan support

This commit is contained in:
siddontang 2014-10-20 22:36:16 +08:00
parent c527d8ebcb
commit f09e43ad99
19 changed files with 333 additions and 128 deletions

View File

@ -1,4 +1,4 @@
//This file was generated by .tools/generate_commands.py on Fri Oct 17 2014 11:39:51 +0800
//This file was generated by .tools/generate_commands.py on Mon Oct 20 2014 22:35:33 +0800
package main
var helpCommands = [][]string{
@ -16,6 +16,7 @@ var helpCommands = [][]string{
{"BRPOP", "key [key ...] timeout", "List"},
{"BSETBIT", "key offset value", "Bitmap"},
{"BTTL", "key", "Bitmap"},
{"BXREVSCAN", "key [MATCH match] [COUNT count]", "Bitmap"},
{"BXSCAN", "key [MATCH match] [COUNT count]", "Bitmap"},
{"COMMIT", "-", "Transaction"},
{"CONFIG REWRITE", "-", "Server"},
@ -50,6 +51,7 @@ var helpCommands = [][]string{
{"HSET", "key field value", "Hash"},
{"HTTL", "key", "Hash"},
{"HVALS", "key", "Hash"},
{"HXREVSCAN", "key [MATCH match] [COUNT count]", "Hash"},
{"HXSCAN", "key [MATCH match] [COUNT count]", "Hash"},
{"INCR", "key", "KV"},
{"INCRBY", "key increment", "KV"},
@ -65,6 +67,7 @@ var helpCommands = [][]string{
{"LPUSH", "key value [value ...]", "List"},
{"LRANGE", "key start stop", "List"},
{"LTTL", "key", "List"},
{"LXREVSCAN", "key [MATCH match] [COUNT count]", "List"},
{"LXSCAN", "key [MATCH match] [COUNT count]", "List"},
{"MGET", "key [key ...]", "KV"},
{"MSET", "key value [key value ...]", "KV"},
@ -97,10 +100,12 @@ var helpCommands = [][]string{
{"STTL", "key", "Set"},
{"SUNION", "key [key ...]", "Set"},
{"SUNIONSTORE", "destination key [key ...]", "Set"},
{"SXREVSCAN", "key [MATCH match] [COUNT count]", "Set"},
{"SXSCAN", "key [MATCH match] [COUNT count]", "Set"},
{"SYNC", "logid", "Replication"},
{"TIME", "-", "Server"},
{"TTL", "key", "KV"},
{"XREVSCAN", "key [MATCH match] [COUNT count]", "KV"},
{"XSCAN", "key [MATCH match] [COUNT count]", "KV"},
{"ZADD", "key score member [score member ...]", "ZSet"},
{"ZCARD", "key", "ZSet"},
@ -127,5 +132,6 @@ var helpCommands = [][]string{
{"ZSCORE", "key member", "ZSet"},
{"ZTTL", "key", "ZSet"},
{"ZUNIONSTORE", "destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]", "ZSet"},
{"ZXREVSCAN", "key [MATCH match] [COUNT count]", "ZSet"},
{"ZXSCAN", "key [MATCH match] [COUNT count]", "ZSet"},
}

View File

@ -47,14 +47,14 @@ Transaction API:
## Scan
LedisDB supplies `xscan`, etc, to fetch data iteratively.
LedisDB supplies `xscan`, `xrevscan`, etc, to fetch data iteratively and reverse iteratively.
+ KV: `xscan`
+ Hash: `hxscan`
+ List: `lxscan`
+ Set: `sxscan`
+ Zset: `zxscan`
+ Bitmap: `bxscan`
+ KV: `xscan`, `xrevscan`
+ Hash: `hxscan`, `hxrevscan`
+ List: `lxscan`, `lxrevscan`
+ Set: `sxscan` , `sxrevscan`
+ Zset: `zxscan`, `zxrevscan`
+ Bitmap: `bxscan`, `bxrevscan`
Of course, LedisDB has not implemented all APIs in Redis, you can see full commands in commands.json, commands.doc or [wiki](https://github.com/siddontang/ledisdb/wiki/Commands).

View File

@ -591,6 +591,42 @@
"group": "Bitmap",
"readonly": true
},
"XREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "KV",
"readonly": true
},
"HXREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "Hash",
"readonly": true
},
"LXREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "List",
"readonly": true
},
"SXREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "Set",
"readonly": true
},
"ZXREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "ZSet",
"readonly": true
},
"BXREVSCAN": {
"arguments": "key [MATCH match] [COUNT count]",
"group": "Bitmap",
"readonly": true
},
"FLUSHALL": {
"arguments": "-",

View File

@ -27,6 +27,7 @@ Table of Contents
- [TTL key](#ttl-key)
- [PERSIST key](#persist-key)
- [XSCAN key [MATCH match] [COUNT count]](#xscan-key-match-match-count-count)
- [XREVSCAN key [MATCH match] [COUNT count]](#xrevscan-key-match-match-count-count)
- [Hash](#hash)
- [HDEL key field [field ...]](#hdel-key-field-field-)
- [HEXISTS key field](#hexists-key-field)
@ -46,6 +47,7 @@ Table of Contents
- [HTTL key](#httl-key)
- [HPERSIST key](#hpersist-key)
- [HXSCAN key [MATCH match] [COUNT count]](#hxscan-key-match-match-count-count)
- [HXREVSCAN key [MATCH match] [COUNT count]](#hxrevscan-key-match-match-count-count)
- [List](#list)
- [BLPOP key [key ...] timeout](#blpop-key-key--timeout)
- [BRPOP key [key ...] timeout](#brpop-key-key--timeout)
@ -63,6 +65,7 @@ Table of Contents
- [LTTL key](#lttl-key)
- [LPERSIST key](#lpersist-key)
- [LXSCAN key [MATCH match] [COUNT count]](#lxscan-key-match-match-count-count)
- [LXREVSCAN key [MATCH match] [COUNT count]](#lxrevscan-key-match-match-count-count)
- [Set](#set)
- [SADD key member [member ...]](#sadd-key-member-member-)
- [SCARD key](#scard-key)
@ -82,6 +85,7 @@ Table of Contents
- [STTL key](#sttl-key)
- [SPERSIST key](#spersist-key)
- [SXSCAN key [MATCH match] [COUNT count]](#sxscan-key-match-match-count-count)
- [SXREVSCAN key [MATCH match] [COUNT count]](#sxrevscan-key-match-match-count-count)
- [ZSet](#zset)
- [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-)
- [ZCARD key](#zcard-key)
@ -108,6 +112,7 @@ Table of Contents
- [ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
](#zinterstore-destination-numkeys-key-key--weights-weight-weight--aggregate-summinmax)
- [ZXSCAN key [MATCH match] [COUNT count]](#zxscan-key-match-match-count-count)
- [ZXREVSCAN key [MATCH match] [COUNT count]](#zxrevscan-key-match-match-count-count)
- [ZRANGEBYLEX key min max [LIMIT offset count]](#zrangebylex-key-min-max-limit-offset-count)
- [ZREMRANGEBYLEX key min max](#zremrangebylex-key-min-max)
- [ZLEXCOUNT key min max](#zlexcount-key-min-max)
@ -123,6 +128,7 @@ Table of Contents
- [BTTL key](#bttl-key)
- [BPERSIST key](#bpersist-key)
- [BXSCAN key [MATCH match] [COUNT count]](#bxscan-key-match-match-count-count)
- [BXREVSCAN key [MATCH match] [COUNT count]](#bxrevscan-key-match-match-count-count)
- [Replication](#replication)
- [SLAVEOF host port [RESTART] [READONLY]](#slaveof-host-port-restart-readonly)
- [FULLSYNC [NEW]](#fullsync-new)
@ -514,6 +520,45 @@ ledis>xscan "c" count 1
2) []
```
### XREVSCAN key [MATCH match] [COUNT count]
Reverse iterate KV keys incrementally.
Key is the start for the current iteration.
Match is the regexp for checking matched key.
Count is the maximum retrieved elememts number, default is 10.
**Return value**
an array of two values, first value is the key for next iteration, second value is an array of elements.
**Examples**
```
ledis>set a 1
OK
ledis>set b 2
OK
ledis>set c 3
OK
127.0.0.1:6380>xrevscan ""
1) ""
2) ["c" "b" "a"]
ledis>xrevscan "" count 1
1) "c"
2) ["c"]
ledis>xrevscan "c" count 1
1) "b"
2) ["b"]
ledis>xrevscan "b" count 1
1) "a"
2) ["a"]
ledis>xrevscan "a" count 1
1) ""
2) []
```
## Hash
### HDEL key field [field ...]
@ -883,6 +928,12 @@ Iterate Hash keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information.
### HXREVSCAN key [MATCH match] [COUNT count]
Reverse iterate Hash keys incrementally.
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
## List
### BLPOP key [key ...] timeout
@ -1211,6 +1262,12 @@ Iterate list keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information.
### LXREVSCAN key [MATCH match] [COUNT count]
Reverse iterate list keys incrementally.
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
## Set
@ -1640,6 +1697,12 @@ Iterate Set keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information.
### SXREVSCAN key [MATCH match] [COUNT count]
Reverse iterate Set keys incrementally.
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
## ZSet
### ZADD key score member [score member ...]
@ -2265,6 +2328,12 @@ Iterate ZSet keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information.
### ZXREVSCAN key [MATCH match] [COUNT count]
Reverse iterate ZSet keys incrementally.
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
### ZRANGEBYLEX key min max [LIMIT offset count]
When all the elements in a sorted set are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the sorted set at key with a value between min and max.
@ -2496,6 +2565,12 @@ Iterate Bitmap keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information.
### BXREVSCAN key [MATCH match] [COUNT count]
Reverse iterate Bitmap keys incrementally.
See [XREVSCAN](#xrevscan-key-match-match-count-count) for more information.
## Replication

View File

@ -1,8 +1,8 @@
package ledis
import (
"bytes"
"errors"
"github.com/siddontang/ledisdb/store"
"regexp"
)
@ -10,6 +10,15 @@ var errDataType = errors.New("error data type")
var errMetaKey = errors.New("error meta key")
func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.scanGeneric(dataType, key, count, inclusive, match, false)
}
func (db *DB) revscan(dataType byte, key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.scanGeneric(dataType, key, count, inclusive, match, true)
}
func (db *DB) scanGeneric(dataType byte, key []byte, count int,
inclusive bool, match string, reverse bool) ([][]byte, error) {
var minKey, maxKey []byte
var err error
var r *regexp.Regexp
@ -20,40 +29,46 @@ func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match s
}
}
if len(key) > 0 {
if err = checkKeySize(key); err != nil {
tp := store.RangeOpen
if !reverse {
if minKey, err = db.encodeScanMinKey(dataType, key); err != nil {
return nil, err
}
if minKey, err = db.encodeScanKey(dataType, key); err != nil {
if maxKey, err = db.encodeScanMaxKey(dataType, nil); err != nil {
return nil, err
}
if inclusive {
tp = store.RangeROpen
}
} else {
if minKey, err = db.encodeScanMinKey(dataType); err != nil {
if minKey, err = db.encodeScanMinKey(dataType, nil); err != nil {
return nil, err
}
if maxKey, err = db.encodeScanMaxKey(dataType, key); err != nil {
return nil, err
}
}
if maxKey, err = db.encodeScanMaxKey(dataType); err != nil {
return nil, err
if inclusive {
tp = store.RangeLOpen
}
}
if count <= 0 {
count = defaultScanCount
}
v := make([][]byte, 0, count)
it := db.bucket.NewIterator()
it.Seek(minKey)
if !inclusive {
if it.Valid() && bytes.Equal(it.RawKey(), minKey) {
it.Next()
}
var it *store.RangeLimitIterator
if !reverse {
it = db.bucket.RangeIterator(minKey, maxKey, tp)
} else {
it = db.bucket.RevRangeIterator(minKey, maxKey, tp)
}
for i := 0; it.Valid() && i < count && bytes.Compare(it.RawKey(), maxKey) < 0; it.Next() {
v := make([][]byte, 0, count)
for i := 0; it.Valid() && i < count; it.Next() {
if k, err := db.decodeScanKey(dataType, it.Key()); err != nil {
continue
} else if r != nil && !r.Match(k) {
@ -67,11 +82,26 @@ func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match s
return v, nil
}
func (db *DB) encodeScanMinKey(dataType byte) ([]byte, error) {
return db.encodeScanKey(dataType, nil)
func (db *DB) encodeScanMinKey(dataType byte, key []byte) ([]byte, error) {
if len(key) == 0 {
return db.encodeScanKey(dataType, nil)
} else {
if err := checkKeySize(key); err != nil {
return nil, err
}
return db.encodeScanKey(dataType, key)
}
}
func (db *DB) encodeScanMaxKey(dataType byte) ([]byte, error) {
func (db *DB) encodeScanMaxKey(dataType byte, key []byte) ([]byte, error) {
if len(key) > 0 {
if err := checkKeySize(key); err != nil {
return nil, err
}
return db.encodeScanKey(dataType, key)
}
k, err := db.encodeScanKey(dataType, nil)
if err != nil {
return nil, err

View File

@ -4,6 +4,18 @@ import (
"testing"
)
func checkTestScan(t *testing.T, v [][]byte, args ...string) {
if len(v) != len(args) {
t.Fatal(len(v), len(args))
}
for i := range v {
if string(v[i]) != args[i] {
t.Fatalf("%q %q", v, args)
}
}
}
func TestDBScan(t *testing.T) {
db := getTestDB()
@ -15,44 +27,86 @@ func TestDBScan(t *testing.T) {
t.Fatal(len(v))
}
if v, err := db.RevScan(nil, 10, true, ""); err != nil {
t.Fatal(err)
} else if len(v) != 0 {
t.Fatal(len(v))
}
db.Set([]byte("a"), []byte{})
db.Set([]byte("b"), []byte{})
db.Set([]byte("c"), []byte{})
if v, err := db.Scan(nil, 1, true, ""); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "a")
}
if v, err := db.Scan([]byte("a"), 2, false, ""); err != nil {
t.Fatal(err)
} else if len(v) != 2 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "b", "c")
}
if v, err := db.Scan(nil, 3, true, ""); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "a", "b", "c")
}
if v, err := db.Scan(nil, 3, true, "b"); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "b")
}
if v, err := db.Scan(nil, 3, true, "."); err != nil {
t.Fatal(err)
} else if len(v) != 3 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "a", "b", "c")
}
if v, err := db.Scan(nil, 3, true, "a+"); err != nil {
t.Fatal(err)
} else if len(v) != 1 {
t.Fatal(len(v))
} else {
checkTestScan(t, v, "a")
}
if v, err := db.RevScan(nil, 1, true, ""); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "c")
}
if v, err := db.RevScan([]byte("c"), 2, false, ""); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "b", "a")
}
if v, err := db.RevScan(nil, 3, true, ""); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "c", "b", "a")
}
if v, err := db.RevScan(nil, 3, true, "b"); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "b")
}
if v, err := db.RevScan(nil, 3, true, "."); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "c", "b", "a")
}
if v, err := db.RevScan(nil, 3, true, "c+"); err != nil {
t.Fatal(err)
} else {
checkTestScan(t, v, "c")
}
}

View File

@ -913,6 +913,10 @@ func (db *DB) BScan(key []byte, count int, inclusive bool, match string) ([][]by
return db.scan(BitMetaType, key, count, inclusive, match)
}
func (db *DB) BRevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(BitMetaType, key, count, inclusive, match)
}
func (db *DB) bFlush() (drop int64, err error) {
t := db.binBatch
t.Lock()

View File

@ -464,6 +464,10 @@ func (db *DB) HScan(key []byte, count int, inclusive bool, match string) ([][]by
return db.scan(HSizeType, key, count, inclusive, match)
}
func (db *DB) HRevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(HSizeType, key, count, inclusive, match)
}
func (db *DB) HExpire(key []byte, duration int64) (int64, error) {
if duration <= 0 {
return 0, errExpireValue

View File

@ -312,6 +312,11 @@ func (db *DB) Scan(key []byte, count int, inclusive bool, match string) ([][]byt
return db.scan(KVType, key, count, inclusive, match)
}
//if inclusive is true, revscan range (-inf, key] else (inf, key)
func (db *DB) RevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(KVType, key, count, inclusive, match)
}
func (db *DB) Expire(key []byte, duration int64) (int64, error) {
if duration <= 0 {
return 0, errExpireValue

View File

@ -484,6 +484,10 @@ func (db *DB) LScan(key []byte, count int, inclusive bool, match string) ([][]by
return db.scan(LMetaType, key, count, inclusive, match)
}
func (db *DB) LRevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(LMetaType, key, count, inclusive, match)
}
func (db *DB) lEncodeMinKey() []byte {
return db.lEncodeMetaKey(nil)
}

View File

@ -599,3 +599,7 @@ func (db *DB) SPersist(key []byte) (int64, error) {
func (db *DB) SScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.scan(SSizeType, key, count, inclusive, match)
}
func (db *DB) SRevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(SSizeType, key, count, inclusive, match)
}

View File

@ -940,6 +940,10 @@ func (db *DB) ZScan(key []byte, count int, inclusive bool, match string) ([][]by
return db.scan(ZSizeType, key, count, inclusive, match)
}
func (db *DB) ZRevScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.revscan(ZSizeType, key, count, inclusive, match)
}
func (db *DB) ZRangeByLex(key []byte, min []byte, max []byte, rangeType uint8, offset int, count int) ([][]byte, error) {
if min == nil {
min = db.zEncodeStartSetKey(key)

View File

@ -275,24 +275,11 @@ func bpersistCommand(c *client) error {
}
func bxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
return xscanGeneric(c, c.db.BScan)
}
if ay, err := c.db.BScan(key, count, false, 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 bxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.BRevScan)
}
func init() {
@ -308,4 +295,5 @@ func init() {
register("bttl", bttlCommand)
register("bpersist", bpersistCommand)
register("bxscan", bxscanCommand)
register("bxrevscan", bxrevscanCommand)
}

View File

@ -293,24 +293,11 @@ func hpersistCommand(c *client) error {
}
func hxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
return xscanGeneric(c, c.db.HScan)
}
if ay, err := c.db.HScan(key, count, false, 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 hxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.HRevScan)
}
func init() {
@ -335,4 +322,5 @@ func init() {
register("httl", httlCommand)
register("hpersist", hpersistCommand)
register("hxscan", hxscanCommand)
register("hxrevscan", hxrevscanCommand)
}

View File

@ -322,13 +322,14 @@ func parseScanArgs(c *client) (key []byte, match string, count int, err error) {
return
}
func xscanCommand(c *client) error {
func xscanGeneric(c *client,
f func(key []byte, count int, inclusive bool, match string) ([][]byte, error)) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
if ay, err := c.db.Scan(key, count, false, match); err != nil {
if ay, err := f(key, count, false, match); err != nil {
return err
} else {
data := make([]interface{}, 2)
@ -343,6 +344,14 @@ func xscanCommand(c *client) error {
return nil
}
func xscanCommand(c *client) error {
return xscanGeneric(c, c.db.Scan)
}
func xrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.RevScan)
}
func init() {
register("decr", decrCommand)
register("decrby", decrbyCommand)
@ -361,4 +370,5 @@ func init() {
register("ttl", ttlCommand)
register("persist", persistCommand)
register("xscan", xscanCommand)
register("xrevscan", xrevscanCommand)
}

View File

@ -232,24 +232,11 @@ func lpersistCommand(c *client) error {
}
func lxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
return xscanGeneric(c, c.db.LScan)
}
if ay, err := c.db.LScan(key, count, false, 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 lxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.LRevScan)
}
func blpopCommand(c *client) error {
@ -319,4 +306,5 @@ func init() {
register("lttl", lttlCommand)
register("lpersist", lpersistCommand)
register("lxscan", lxscanCommand)
register("lxrevscan", lxrevscanCommand)
}

View File

@ -263,24 +263,11 @@ func spersistCommand(c *client) error {
}
func sxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
return xscanGeneric(c, c.db.SScan)
}
if ay, err := c.db.SScan(key, count, false, 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 sxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.SRevScan)
}
func init() {
@ -302,4 +289,5 @@ func init() {
register("sttl", sttlCommand)
register("spersist", spersistCommand)
register("sxscan", sxscanCommand)
register("sxrevscan", sxrevscanCommand)
}

View File

@ -642,24 +642,11 @@ func zinterstoreCommand(c *client) error {
}
func zxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c)
if err != nil {
return err
}
return xscanGeneric(c, c.db.ZScan)
}
if ay, err := c.db.ZScan(key, count, false, 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 zxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.ZRevScan)
}
func zparseMemberRange(minBuf []byte, maxBuf []byte) (min []byte, max []byte, rangeType uint8, err error) {
@ -816,4 +803,5 @@ func init() {
register("zttl", zttlCommand)
register("zpersist", zpersistCommand)
register("zxscan", zxscanCommand)
register("zxrevscan", zxrevscanCommand)
}

View File

@ -77,6 +77,29 @@ func checkScan(t *testing.T, c *ledis.Client, cmd string) {
}
func checkRevScan(t *testing.T, c *ledis.Client, cmd string) {
if ay, err := ledis.Values(c.Do(cmd, "", "count", 5)); err != nil {
t.Fatal(err)
} else if len(ay) != 2 {
t.Fatal(len(ay))
} else if n := ay[0].([]byte); string(n) != "5" {
t.Fatal(string(n))
} else {
checkScanValues(t, ay[1], 9, 8, 7, 6, 5)
}
if ay, err := ledis.Values(c.Do(cmd, "5", "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], 4, 3, 2, 1, 0)
}
}
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 {
@ -85,6 +108,7 @@ func testKVScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "xscan")
checkRevScan(t, c, "xrevscan")
}
func testHashScan(t *testing.T, c *ledis.Client) {
@ -95,6 +119,7 @@ func testHashScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "hxscan")
checkRevScan(t, c, "hxrevscan")
}
func testListScan(t *testing.T, c *ledis.Client) {
@ -105,6 +130,7 @@ func testListScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "lxscan")
checkRevScan(t, c, "lxrevscan")
}
func testZSetScan(t *testing.T, c *ledis.Client) {
@ -115,6 +141,7 @@ func testZSetScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "zxscan")
checkRevScan(t, c, "zxrevscan")
}
func testSetScan(t *testing.T, c *ledis.Client) {
@ -125,6 +152,7 @@ func testSetScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "sxscan")
checkRevScan(t, c, "sxrevscan")
}
func testBitScan(t *testing.T, c *ledis.Client) {
@ -135,4 +163,5 @@ func testBitScan(t *testing.T, c *ledis.Client) {
}
checkScan(t, c, "bxscan")
checkRevScan(t, c, "bxrevscan")
}