diff --git a/cmd/ledis-cli/const.go b/cmd/ledis-cli/const.go index 7b1be6a..790a77b 100644 --- a/cmd/ledis-cli/const.go +++ b/cmd/ledis-cli/const.go @@ -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"}, } diff --git a/doc/DiffRedis.md b/doc/DiffRedis.md index ee1618c..cdcb89b 100644 --- a/doc/DiffRedis.md +++ b/doc/DiffRedis.md @@ -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). \ No newline at end of file diff --git a/doc/commands.json b/doc/commands.json index 2f7a24c..b03e7f9 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -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": "-", diff --git a/doc/commands.md b/doc/commands.md index 18bfa27..9dcdca6 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -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 diff --git a/ledis/scan.go b/ledis/scan.go index f7fca13..9e8e235 100644 --- a/ledis/scan.go +++ b/ledis/scan.go @@ -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 diff --git a/ledis/scan_test.go b/ledis/scan_test.go index 5753b7f..7a60da6 100644 --- a/ledis/scan_test.go +++ b/ledis/scan_test.go @@ -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") } } diff --git a/ledis/t_bit.go b/ledis/t_bit.go index ab104db..771ef90 100644 --- a/ledis/t_bit.go +++ b/ledis/t_bit.go @@ -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() diff --git a/ledis/t_hash.go b/ledis/t_hash.go index a2e0bd3..98ee24f 100644 --- a/ledis/t_hash.go +++ b/ledis/t_hash.go @@ -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 diff --git a/ledis/t_kv.go b/ledis/t_kv.go index 14d477b..497dcf2 100644 --- a/ledis/t_kv.go +++ b/ledis/t_kv.go @@ -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 diff --git a/ledis/t_list.go b/ledis/t_list.go index 86d9a15..7f66bed 100644 --- a/ledis/t_list.go +++ b/ledis/t_list.go @@ -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) } diff --git a/ledis/t_set.go b/ledis/t_set.go index 2eb6c4c..d164752 100644 --- a/ledis/t_set.go +++ b/ledis/t_set.go @@ -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) +} diff --git a/ledis/t_zset.go b/ledis/t_zset.go index dc028c0..4719171 100644 --- a/ledis/t_zset.go +++ b/ledis/t_zset.go @@ -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) diff --git a/server/cmd_bit.go b/server/cmd_bit.go index 22d34fe..e5485c5 100644 --- a/server/cmd_bit.go +++ b/server/cmd_bit.go @@ -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) } diff --git a/server/cmd_hash.go b/server/cmd_hash.go index e3094fd..69e1c5e 100644 --- a/server/cmd_hash.go +++ b/server/cmd_hash.go @@ -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) } diff --git a/server/cmd_kv.go b/server/cmd_kv.go index f7a90d8..c62cc18 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -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) } diff --git a/server/cmd_list.go b/server/cmd_list.go index 0613836..1db77a1 100644 --- a/server/cmd_list.go +++ b/server/cmd_list.go @@ -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) } diff --git a/server/cmd_set.go b/server/cmd_set.go index 4992bbf..a0e14e8 100644 --- a/server/cmd_set.go +++ b/server/cmd_set.go @@ -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) } diff --git a/server/cmd_zset.go b/server/cmd_zset.go index 3c5abcb..0fddc95 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -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) } diff --git a/server/scan_test.go b/server/scan_test.go index ed9b71c..c475f1e 100644 --- a/server/scan_test.go +++ b/server/scan_test.go @@ -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") }