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 package main
var helpCommands = [][]string{ var helpCommands = [][]string{
@ -16,6 +16,7 @@ var helpCommands = [][]string{
{"BRPOP", "key [key ...] timeout", "List"}, {"BRPOP", "key [key ...] timeout", "List"},
{"BSETBIT", "key offset value", "Bitmap"}, {"BSETBIT", "key offset value", "Bitmap"},
{"BTTL", "key", "Bitmap"}, {"BTTL", "key", "Bitmap"},
{"BXREVSCAN", "key [MATCH match] [COUNT count]", "Bitmap"},
{"BXSCAN", "key [MATCH match] [COUNT count]", "Bitmap"}, {"BXSCAN", "key [MATCH match] [COUNT count]", "Bitmap"},
{"COMMIT", "-", "Transaction"}, {"COMMIT", "-", "Transaction"},
{"CONFIG REWRITE", "-", "Server"}, {"CONFIG REWRITE", "-", "Server"},
@ -50,6 +51,7 @@ var helpCommands = [][]string{
{"HSET", "key field value", "Hash"}, {"HSET", "key field value", "Hash"},
{"HTTL", "key", "Hash"}, {"HTTL", "key", "Hash"},
{"HVALS", "key", "Hash"}, {"HVALS", "key", "Hash"},
{"HXREVSCAN", "key [MATCH match] [COUNT count]", "Hash"},
{"HXSCAN", "key [MATCH match] [COUNT count]", "Hash"}, {"HXSCAN", "key [MATCH match] [COUNT count]", "Hash"},
{"INCR", "key", "KV"}, {"INCR", "key", "KV"},
{"INCRBY", "key increment", "KV"}, {"INCRBY", "key increment", "KV"},
@ -65,6 +67,7 @@ var helpCommands = [][]string{
{"LPUSH", "key value [value ...]", "List"}, {"LPUSH", "key value [value ...]", "List"},
{"LRANGE", "key start stop", "List"}, {"LRANGE", "key start stop", "List"},
{"LTTL", "key", "List"}, {"LTTL", "key", "List"},
{"LXREVSCAN", "key [MATCH match] [COUNT count]", "List"},
{"LXSCAN", "key [MATCH match] [COUNT count]", "List"}, {"LXSCAN", "key [MATCH match] [COUNT count]", "List"},
{"MGET", "key [key ...]", "KV"}, {"MGET", "key [key ...]", "KV"},
{"MSET", "key value [key value ...]", "KV"}, {"MSET", "key value [key value ...]", "KV"},
@ -97,10 +100,12 @@ var helpCommands = [][]string{
{"STTL", "key", "Set"}, {"STTL", "key", "Set"},
{"SUNION", "key [key ...]", "Set"}, {"SUNION", "key [key ...]", "Set"},
{"SUNIONSTORE", "destination key [key ...]", "Set"}, {"SUNIONSTORE", "destination key [key ...]", "Set"},
{"SXREVSCAN", "key [MATCH match] [COUNT count]", "Set"},
{"SXSCAN", "key [MATCH match] [COUNT count]", "Set"}, {"SXSCAN", "key [MATCH match] [COUNT count]", "Set"},
{"SYNC", "logid", "Replication"}, {"SYNC", "logid", "Replication"},
{"TIME", "-", "Server"}, {"TIME", "-", "Server"},
{"TTL", "key", "KV"}, {"TTL", "key", "KV"},
{"XREVSCAN", "key [MATCH match] [COUNT count]", "KV"},
{"XSCAN", "key [MATCH match] [COUNT count]", "KV"}, {"XSCAN", "key [MATCH match] [COUNT count]", "KV"},
{"ZADD", "key score member [score member ...]", "ZSet"}, {"ZADD", "key score member [score member ...]", "ZSet"},
{"ZCARD", "key", "ZSet"}, {"ZCARD", "key", "ZSet"},
@ -127,5 +132,6 @@ var helpCommands = [][]string{
{"ZSCORE", "key member", "ZSet"}, {"ZSCORE", "key member", "ZSet"},
{"ZTTL", "key", "ZSet"}, {"ZTTL", "key", "ZSet"},
{"ZUNIONSTORE", "destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]", "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"}, {"ZXSCAN", "key [MATCH match] [COUNT count]", "ZSet"},
} }

View File

@ -47,14 +47,14 @@ Transaction API:
## Scan ## Scan
LedisDB supplies `xscan`, etc, to fetch data iteratively. LedisDB supplies `xscan`, `xrevscan`, etc, to fetch data iteratively and reverse iteratively.
+ KV: `xscan` + KV: `xscan`, `xrevscan`
+ Hash: `hxscan` + Hash: `hxscan`, `hxrevscan`
+ List: `lxscan` + List: `lxscan`, `lxrevscan`
+ Set: `sxscan` + Set: `sxscan` , `sxrevscan`
+ Zset: `zxscan` + Zset: `zxscan`, `zxrevscan`
+ Bitmap: `bxscan` + 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). 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", "group": "Bitmap",
"readonly": true "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": { "FLUSHALL": {
"arguments": "-", "arguments": "-",

View File

@ -27,6 +27,7 @@ Table of Contents
- [TTL key](#ttl-key) - [TTL key](#ttl-key)
- [PERSIST key](#persist-key) - [PERSIST key](#persist-key)
- [XSCAN key [MATCH match] [COUNT count]](#xscan-key-match-match-count-count) - [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) - [Hash](#hash)
- [HDEL key field [field ...]](#hdel-key-field-field-) - [HDEL key field [field ...]](#hdel-key-field-field-)
- [HEXISTS key field](#hexists-key-field) - [HEXISTS key field](#hexists-key-field)
@ -46,6 +47,7 @@ Table of Contents
- [HTTL key](#httl-key) - [HTTL key](#httl-key)
- [HPERSIST key](#hpersist-key) - [HPERSIST key](#hpersist-key)
- [HXSCAN key [MATCH match] [COUNT count]](#hxscan-key-match-match-count-count) - [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) - [List](#list)
- [BLPOP key [key ...] timeout](#blpop-key-key--timeout) - [BLPOP key [key ...] timeout](#blpop-key-key--timeout)
- [BRPOP key [key ...] timeout](#brpop-key-key--timeout) - [BRPOP key [key ...] timeout](#brpop-key-key--timeout)
@ -63,6 +65,7 @@ Table of Contents
- [LTTL key](#lttl-key) - [LTTL key](#lttl-key)
- [LPERSIST key](#lpersist-key) - [LPERSIST key](#lpersist-key)
- [LXSCAN key [MATCH match] [COUNT count]](#lxscan-key-match-match-count-count) - [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) - [Set](#set)
- [SADD key member [member ...]](#sadd-key-member-member-) - [SADD key member [member ...]](#sadd-key-member-member-)
- [SCARD key](#scard-key) - [SCARD key](#scard-key)
@ -82,6 +85,7 @@ Table of Contents
- [STTL key](#sttl-key) - [STTL key](#sttl-key)
- [SPERSIST key](#spersist-key) - [SPERSIST key](#spersist-key)
- [SXSCAN key [MATCH match] [COUNT count]](#sxscan-key-match-match-count-count) - [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) - [ZSet](#zset)
- [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-) - [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-)
- [ZCARD key](#zcard-key) - [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 SUM|MIN|MAX]
](#zinterstore-destination-numkeys-key-key--weights-weight-weight--aggregate-summinmax) ](#zinterstore-destination-numkeys-key-key--weights-weight-weight--aggregate-summinmax)
- [ZXSCAN key [MATCH match] [COUNT count]](#zxscan-key-match-match-count-count) - [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) - [ZRANGEBYLEX key min max [LIMIT offset count]](#zrangebylex-key-min-max-limit-offset-count)
- [ZREMRANGEBYLEX key min max](#zremrangebylex-key-min-max) - [ZREMRANGEBYLEX key min max](#zremrangebylex-key-min-max)
- [ZLEXCOUNT key min max](#zlexcount-key-min-max) - [ZLEXCOUNT key min max](#zlexcount-key-min-max)
@ -123,6 +128,7 @@ Table of Contents
- [BTTL key](#bttl-key) - [BTTL key](#bttl-key)
- [BPERSIST key](#bpersist-key) - [BPERSIST key](#bpersist-key)
- [BXSCAN key [MATCH match] [COUNT count]](#bxscan-key-match-match-count-count) - [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) - [Replication](#replication)
- [SLAVEOF host port [RESTART] [READONLY]](#slaveof-host-port-restart-readonly) - [SLAVEOF host port [RESTART] [READONLY]](#slaveof-host-port-restart-readonly)
- [FULLSYNC [NEW]](#fullsync-new) - [FULLSYNC [NEW]](#fullsync-new)
@ -514,6 +520,45 @@ ledis>xscan "c" count 1
2) [] 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 ## Hash
### HDEL key field [field ...] ### HDEL key field [field ...]
@ -883,6 +928,12 @@ Iterate Hash keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information. 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 ## List
### BLPOP key [key ...] timeout ### BLPOP key [key ...] timeout
@ -1211,6 +1262,12 @@ Iterate list keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information. 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 ## Set
@ -1640,6 +1697,12 @@ Iterate Set keys incrementally.
See [XSCAN](#xscan-key-match-match-count-count) for more information. 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 ## ZSet
### ZADD key score member [score member ...] ### 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. 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] ### 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. 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. 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 ## Replication

View File

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

View File

@ -4,6 +4,18 @@ import (
"testing" "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) { func TestDBScan(t *testing.T) {
db := getTestDB() db := getTestDB()
@ -15,44 +27,86 @@ func TestDBScan(t *testing.T) {
t.Fatal(len(v)) 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("a"), []byte{})
db.Set([]byte("b"), []byte{}) db.Set([]byte("b"), []byte{})
db.Set([]byte("c"), []byte{}) db.Set([]byte("c"), []byte{})
if v, err := db.Scan(nil, 1, true, ""); err != nil { if v, err := db.Scan(nil, 1, true, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 1 { } else {
t.Fatal(len(v)) checkTestScan(t, v, "a")
} }
if v, err := db.Scan([]byte("a"), 2, false, ""); err != nil { if v, err := db.Scan([]byte("a"), 2, false, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 2 { } else {
t.Fatal(len(v)) checkTestScan(t, v, "b", "c")
} }
if v, err := db.Scan(nil, 3, true, ""); err != nil { if v, err := db.Scan(nil, 3, true, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 3 { } else {
t.Fatal(len(v)) checkTestScan(t, v, "a", "b", "c")
} }
if v, err := db.Scan(nil, 3, true, "b"); err != nil { if v, err := db.Scan(nil, 3, true, "b"); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 1 { } else {
t.Fatal(len(v)) checkTestScan(t, v, "b")
} }
if v, err := db.Scan(nil, 3, true, "."); err != nil { if v, err := db.Scan(nil, 3, true, "."); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 3 { } else {
t.Fatal(len(v)) checkTestScan(t, v, "a", "b", "c")
} }
if v, err := db.Scan(nil, 3, true, "a+"); err != nil { if v, err := db.Scan(nil, 3, true, "a+"); err != nil {
t.Fatal(err) t.Fatal(err)
} else if len(v) != 1 { } else {
t.Fatal(len(v)) 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) 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) { func (db *DB) bFlush() (drop int64, err error) {
t := db.binBatch t := db.binBatch
t.Lock() 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) 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) { func (db *DB) HExpire(key []byte, duration int64) (int64, error) {
if duration <= 0 { if duration <= 0 {
return 0, errExpireValue 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) 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) { func (db *DB) Expire(key []byte, duration int64) (int64, error) {
if duration <= 0 { if duration <= 0 {
return 0, errExpireValue 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) 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 { func (db *DB) lEncodeMinKey() []byte {
return db.lEncodeMetaKey(nil) 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) { func (db *DB) SScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
return db.scan(SSizeType, key, count, inclusive, match) 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) 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) { func (db *DB) ZRangeByLex(key []byte, min []byte, max []byte, rangeType uint8, offset int, count int) ([][]byte, error) {
if min == nil { if min == nil {
min = db.zEncodeStartSetKey(key) min = db.zEncodeStartSetKey(key)

View File

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

View File

@ -293,24 +293,11 @@ func hpersistCommand(c *client) error {
} }
func hxscanCommand(c *client) error { func hxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c) return xscanGeneric(c, c.db.HScan)
if err != nil { }
return err
}
if ay, err := c.db.HScan(key, count, false, match); err != nil { func hxrevscanCommand(c *client) error {
return err return xscanGeneric(c, c.db.HRevScan)
} 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() {
@ -335,4 +322,5 @@ func init() {
register("httl", httlCommand) register("httl", httlCommand)
register("hpersist", hpersistCommand) register("hpersist", hpersistCommand)
register("hxscan", hxscanCommand) 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 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) key, match, count, err := parseScanArgs(c)
if err != nil { if err != nil {
return err 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 return err
} else { } else {
data := make([]interface{}, 2) data := make([]interface{}, 2)
@ -343,6 +344,14 @@ func xscanCommand(c *client) error {
return nil 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() { func init() {
register("decr", decrCommand) register("decr", decrCommand)
register("decrby", decrbyCommand) register("decrby", decrbyCommand)
@ -361,4 +370,5 @@ func init() {
register("ttl", ttlCommand) register("ttl", ttlCommand)
register("persist", persistCommand) register("persist", persistCommand)
register("xscan", xscanCommand) register("xscan", xscanCommand)
register("xrevscan", xrevscanCommand)
} }

View File

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

View File

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

View File

@ -642,24 +642,11 @@ func zinterstoreCommand(c *client) error {
} }
func zxscanCommand(c *client) error { func zxscanCommand(c *client) error {
key, match, count, err := parseScanArgs(c) return xscanGeneric(c, c.db.ZScan)
if err != nil { }
return err
}
if ay, err := c.db.ZScan(key, count, false, match); err != nil { func zxrevscanCommand(c *client) error {
return err return xscanGeneric(c, c.db.ZRevScan)
} 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 zparseMemberRange(minBuf []byte, maxBuf []byte) (min []byte, max []byte, rangeType uint8, err error) { func zparseMemberRange(minBuf []byte, maxBuf []byte) (min []byte, max []byte, rangeType uint8, err error) {
@ -816,4 +803,5 @@ func init() {
register("zttl", zttlCommand) register("zttl", zttlCommand)
register("zpersist", zpersistCommand) register("zpersist", zpersistCommand)
register("zxscan", zxscanCommand) 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) { func testKVScan(t *testing.T, c *ledis.Client) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if _, err := c.Do("set", fmt.Sprintf("%d", i), []byte("value")); err != nil { 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") checkScan(t, c, "xscan")
checkRevScan(t, c, "xrevscan")
} }
func testHashScan(t *testing.T, c *ledis.Client) { func testHashScan(t *testing.T, c *ledis.Client) {
@ -95,6 +119,7 @@ func testHashScan(t *testing.T, c *ledis.Client) {
} }
checkScan(t, c, "hxscan") checkScan(t, c, "hxscan")
checkRevScan(t, c, "hxrevscan")
} }
func testListScan(t *testing.T, c *ledis.Client) { func testListScan(t *testing.T, c *ledis.Client) {
@ -105,6 +130,7 @@ func testListScan(t *testing.T, c *ledis.Client) {
} }
checkScan(t, c, "lxscan") checkScan(t, c, "lxscan")
checkRevScan(t, c, "lxrevscan")
} }
func testZSetScan(t *testing.T, c *ledis.Client) { func testZSetScan(t *testing.T, c *ledis.Client) {
@ -115,6 +141,7 @@ func testZSetScan(t *testing.T, c *ledis.Client) {
} }
checkScan(t, c, "zxscan") checkScan(t, c, "zxscan")
checkRevScan(t, c, "zxrevscan")
} }
func testSetScan(t *testing.T, c *ledis.Client) { func testSetScan(t *testing.T, c *ledis.Client) {
@ -125,6 +152,7 @@ func testSetScan(t *testing.T, c *ledis.Client) {
} }
checkScan(t, c, "sxscan") checkScan(t, c, "sxscan")
checkRevScan(t, c, "sxrevscan")
} }
func testBitScan(t *testing.T, c *ledis.Client) { func testBitScan(t *testing.T, c *ledis.Client) {
@ -135,4 +163,5 @@ func testBitScan(t *testing.T, c *ledis.Client) {
} }
checkScan(t, c, "bxscan") checkScan(t, c, "bxscan")
checkRevScan(t, c, "bxrevscan")
} }