forked from mirror/ledisdb
add redis-compatible HSCAN/SSCAN/ZSCAN commands (#321)
This commit is contained in:
parent
57a07edd1a
commit
015411f5ce
|
@ -1,4 +1,4 @@
|
|||
//This file was generated by .tools/generate_commands.py on Sat Mar 14 2015 08:58:32 +0800
|
||||
//This file was generated by .tools/generate_commands.py on Sat Oct 28 2017 18:15:49 -0500
|
||||
package main
|
||||
|
||||
var helpCommands = [][]string{
|
||||
|
@ -43,6 +43,7 @@ var helpCommands = [][]string{
|
|||
{"HMGET", "key field [field ...]", "Hash"},
|
||||
{"HMSET", "key field value [field value ...]", "Hash"},
|
||||
{"HPERSIST", "key", "Hash"},
|
||||
{"HSCAN", "key cursor [MATCH match] [COUNT count] [ASC|DESC]", "Hash"},
|
||||
{"HSET", "key field value", "Hash"},
|
||||
{"HTTL", "key", "Hash"},
|
||||
{"HVALS", "key", "Hash"},
|
||||
|
@ -96,6 +97,7 @@ var helpCommands = [][]string{
|
|||
{"SMEMBERS", "key", "Set"},
|
||||
{"SPERSIST", "key", "Set"},
|
||||
{"SREM", "key member [member ...]", "Set"},
|
||||
{"SSCAN", "key cursor [MATCH match] [COUNT count] [ASC|DESC]", "Set"},
|
||||
{"STRLEN", "key", "KV"},
|
||||
{"STTL", "key", "Set"},
|
||||
{"SUNION", "key [key ...]", "Set"},
|
||||
|
@ -134,6 +136,7 @@ var helpCommands = [][]string{
|
|||
{"ZREVRANGE", "key start stop [WITHSCORES]", "ZSet"},
|
||||
{"ZREVRANGEBYSCORE", "key max min [WITHSCORES][LIMIT offset count]", "ZSet"},
|
||||
{"ZREVRANK", "key member", "ZSet"},
|
||||
{"ZSCAN", "key cursor [MATCH match] [COUNT count] [ASC|DESC]", "ZSet"},
|
||||
{"ZSCORE", "key member", "ZSet"},
|
||||
{"ZTTL", "key", "ZSet"},
|
||||
{"ZUNIONSTORE", "destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]", "ZSet"},
|
||||
|
|
|
@ -32,13 +32,10 @@ ZSet only support int64 score, not double in Redis.
|
|||
|
||||
## Scan
|
||||
|
||||
LedisDB supplies `xscan`, `xhscan`, `xsscan`, `xzscan` to fetch data iteratively and reverse iteratively.
|
||||
LedisDB supplies `xscan` instead of `scan` to fetch keys iteratively and reverse iteratively.
|
||||
|
||||
```
|
||||
XSCAN type cursor [MATCH match] [COUNT count]
|
||||
XHSCAN key cursor [MATCH match] [COUNT count]
|
||||
XSSCAN key cursor [MATCH match] [COUNT count]
|
||||
XZSCAN key cursor [MATCH match] [COUNT count]
|
||||
```
|
||||
|
||||
## DUMP
|
||||
|
|
|
@ -115,6 +115,11 @@
|
|||
"group": "Hash",
|
||||
"readonly": false
|
||||
},
|
||||
"HSCAN": {
|
||||
"arguments": "key cursor [MATCH match] [COUNT count] [ASC|DESC]",
|
||||
"group": "Hash",
|
||||
"readonly": true
|
||||
},
|
||||
"HPERSIST": {
|
||||
"arguments": "key",
|
||||
"group": "Hash",
|
||||
|
@ -310,6 +315,11 @@
|
|||
"group": "Set",
|
||||
"readonly": true
|
||||
},
|
||||
"SSCAN": {
|
||||
"arguments": "key cursor [MATCH match] [COUNT count] [ASC|DESC]",
|
||||
"group": "Set",
|
||||
"readonly": true
|
||||
},
|
||||
"SREM": {
|
||||
"arguments": "key member [member ...]",
|
||||
"group": "Set",
|
||||
|
@ -450,6 +460,11 @@
|
|||
"group": "ZSet",
|
||||
"readonly": true
|
||||
},
|
||||
"ZSCAN": {
|
||||
"arguments": "key cursor [MATCH match] [COUNT count] [ASC|DESC]",
|
||||
"group": "ZSet",
|
||||
"readonly": true
|
||||
},
|
||||
"ZSCORE": {
|
||||
"arguments": "key member",
|
||||
"group": "ZSet",
|
||||
|
|
|
@ -48,6 +48,7 @@ Most of the Ledisdb's commands are the same as Redis's, you can see the redis co
|
|||
- [HLEN key](#hlen-key)
|
||||
- [HMGET key field [field ...]](#hmget-key-field-field-)
|
||||
- [HMSET key field value [field value ...]](#hmset-key-field-value-field-value-)
|
||||
- [HSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]](#hscan-key-cursor-match-match-count-count-asc|desc)
|
||||
- [HSET key field value](#hset-key-field-value)
|
||||
- [HVALS key](#hvals-key)
|
||||
- [HCLEAR key](#hclear-key)
|
||||
|
@ -88,6 +89,7 @@ Most of the Ledisdb's commands are the same as Redis's, you can see the redis co
|
|||
- [SISMEMBER key member](#sismember--key-member)
|
||||
- [SMEMBERS key](#smembers-key)
|
||||
- [SREM key member [member ...]](#srem--key-member-member-)
|
||||
- [SSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]](#sscan-key-cursor-match-match-count-count-asc|desc)
|
||||
- [SUNION key [key ...]](#sunion-key-key-)
|
||||
- [SUNIONSTORE destination key [key]](#sunionstore-destination-key-key)
|
||||
- [SCLEAR key](#sclear-key)
|
||||
|
@ -112,6 +114,7 @@ Most of the Ledisdb's commands are the same as Redis's, you can see the redis co
|
|||
- [ZREVRANGE key start stop [WITHSCORES]](#zrevrange-key-start-stop-withscores)
|
||||
- [ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]](#zrevrangebyscore--key-max-min-withscores-limit-offset-count)
|
||||
- [ZREVRANK key member](#zrevrank-key-member)
|
||||
- [ZSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]](#zscan-key-cursor-match-match-count-count-asc|desc)
|
||||
- [ZSCORE key member](#zscore-key-member)
|
||||
- [ZCLEAR key](#zclear-key)
|
||||
- [ZMCLEAR key [key ...]](#zmclear-key-key-)
|
||||
|
@ -730,6 +733,12 @@ ledis> HMGET myhash field1 field2
|
|||
2) "world"
|
||||
```
|
||||
|
||||
### HSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
|
||||
Same like XHSCAN, but made redis compatible.
|
||||
Meaning that the initial cursor has to be `"0"`,
|
||||
and the final cursor will be `"0"` as well.
|
||||
|
||||
### HSET key field value
|
||||
|
||||
Sets field in the hash stored at key to value. If key does not exists, a new hash key is created.
|
||||
|
@ -1489,6 +1498,12 @@ ledis> SMEMBERS myset
|
|||
2) "two"
|
||||
```
|
||||
|
||||
### SSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
|
||||
Same like XSSCAN, but made redis compatible.
|
||||
Meaning that the initial cursor has to be `"0"`,
|
||||
and the final cursor will be `"0"` as well.
|
||||
|
||||
### SUNION key [key ...]
|
||||
|
||||
Returns the members of the set resulting from the union of all the given sets.
|
||||
|
@ -2089,6 +2104,12 @@ ledis> ZSCORE myzset 'one'
|
|||
1
|
||||
```
|
||||
|
||||
### ZSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
|
||||
Same like XZSCAN, but made redis compatible.
|
||||
Meaning that the initial cursor has to be `"0"`,
|
||||
and the final cursor will be `"0"` as well.
|
||||
|
||||
### ZCLEAR key
|
||||
Delete the specified key
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -10,7 +11,7 @@ import (
|
|||
"github.com/siddontang/ledisdb/ledis"
|
||||
)
|
||||
|
||||
func parseScanArgs(args [][]byte) (cursor []byte, match string, count int, desc bool, err error) {
|
||||
func parseXScanArgs(args [][]byte) (cursor []byte, match string, count int, desc bool, err error) {
|
||||
cursor = args[0]
|
||||
|
||||
args = args[1:]
|
||||
|
@ -56,8 +57,21 @@ func parseScanArgs(args [][]byte) (cursor []byte, match string, count int, desc
|
|||
return
|
||||
}
|
||||
|
||||
func parseScanArgs(args [][]byte) (cursor []byte, match string, count int, desc bool, err error) {
|
||||
cursor, match, count, desc, err = parseXScanArgs(args)
|
||||
if bytes.Compare(cursor, nilCursorRedis) == 0 {
|
||||
cursor = nilCursorLedis
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type scanCommandGroup struct {
|
||||
lastCursor []byte
|
||||
parseArgs func(args [][]byte) (cursor []byte, match string, count int, desc bool, err error)
|
||||
}
|
||||
|
||||
// XSCAN type cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
func xscanCommand(c *client) error {
|
||||
func (scg scanCommandGroup) xscanCommand(c *client) error {
|
||||
args := c.args
|
||||
|
||||
if len(args) < 2 {
|
||||
|
@ -80,7 +94,7 @@ func xscanCommand(c *client) error {
|
|||
return fmt.Errorf("invalid key type %s", args[0])
|
||||
}
|
||||
|
||||
cursor, match, count, desc, err := parseScanArgs(args[1:])
|
||||
cursor, match, count, desc, err := scg.parseArgs(args[1:])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -100,7 +114,7 @@ func xscanCommand(c *client) error {
|
|||
|
||||
data := make([]interface{}, 2)
|
||||
if len(ay) < count {
|
||||
data[0] = []byte("")
|
||||
data[0] = scg.lastCursor
|
||||
} else {
|
||||
data[0] = ay[len(ay)-1]
|
||||
}
|
||||
|
@ -110,7 +124,7 @@ func xscanCommand(c *client) error {
|
|||
}
|
||||
|
||||
// XHSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
func xhscanCommand(c *client) error {
|
||||
func (scg scanCommandGroup) xhscanCommand(c *client) error {
|
||||
args := c.args
|
||||
|
||||
if len(args) < 2 {
|
||||
|
@ -119,7 +133,7 @@ func xhscanCommand(c *client) error {
|
|||
|
||||
key := args[0]
|
||||
|
||||
cursor, match, count, desc, err := parseScanArgs(args[1:])
|
||||
cursor, match, count, desc, err := scg.parseArgs(args[1:])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -139,7 +153,7 @@ func xhscanCommand(c *client) error {
|
|||
|
||||
data := make([]interface{}, 2)
|
||||
if len(ay) < count {
|
||||
data[0] = []byte("")
|
||||
data[0] = scg.lastCursor
|
||||
} else {
|
||||
data[0] = ay[len(ay)-1].Field
|
||||
}
|
||||
|
@ -157,7 +171,7 @@ func xhscanCommand(c *client) error {
|
|||
}
|
||||
|
||||
// XSSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
func xsscanCommand(c *client) error {
|
||||
func (scg scanCommandGroup) xsscanCommand(c *client) error {
|
||||
args := c.args
|
||||
|
||||
if len(args) < 2 {
|
||||
|
@ -166,7 +180,7 @@ func xsscanCommand(c *client) error {
|
|||
|
||||
key := args[0]
|
||||
|
||||
cursor, match, count, desc, err := parseScanArgs(args[1:])
|
||||
cursor, match, count, desc, err := scg.parseArgs(args[1:])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -186,7 +200,7 @@ func xsscanCommand(c *client) error {
|
|||
|
||||
data := make([]interface{}, 2)
|
||||
if len(ay) < count {
|
||||
data[0] = []byte("")
|
||||
data[0] = scg.lastCursor
|
||||
} else {
|
||||
data[0] = ay[len(ay)-1]
|
||||
}
|
||||
|
@ -198,7 +212,7 @@ func xsscanCommand(c *client) error {
|
|||
}
|
||||
|
||||
// XZSCAN key cursor [MATCH match] [COUNT count] [ASC|DESC]
|
||||
func xzscanCommand(c *client) error {
|
||||
func (scg scanCommandGroup) xzscanCommand(c *client) error {
|
||||
args := c.args
|
||||
|
||||
if len(args) < 2 {
|
||||
|
@ -207,7 +221,7 @@ func xzscanCommand(c *client) error {
|
|||
|
||||
key := args[0]
|
||||
|
||||
cursor, match, count, desc, err := parseScanArgs(args[1:])
|
||||
cursor, match, count, desc, err := scg.parseArgs(args[1:])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -227,7 +241,7 @@ func xzscanCommand(c *client) error {
|
|||
|
||||
data := make([]interface{}, 2)
|
||||
if len(ay) < count {
|
||||
data[0] = []byte("")
|
||||
data[0] = scg.lastCursor
|
||||
} else {
|
||||
data[0] = ay[len(ay)-1].Member
|
||||
}
|
||||
|
@ -244,9 +258,23 @@ func xzscanCommand(c *client) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
xScanGroup = scanCommandGroup{nilCursorLedis, parseXScanArgs}
|
||||
scanGroup = scanCommandGroup{nilCursorRedis, parseScanArgs}
|
||||
)
|
||||
|
||||
var (
|
||||
nilCursorLedis = []byte("")
|
||||
nilCursorRedis = []byte("0")
|
||||
)
|
||||
|
||||
func init() {
|
||||
register("xscan", xscanCommand)
|
||||
register("xhscan", xhscanCommand)
|
||||
register("xsscan", xsscanCommand)
|
||||
register("xzscan", xzscanCommand)
|
||||
register("hscan", scanGroup.xhscanCommand)
|
||||
register("sscan", scanGroup.xsscanCommand)
|
||||
register("zscan", scanGroup.xzscanCommand)
|
||||
|
||||
register("xscan", xScanGroup.xscanCommand)
|
||||
register("xhscan", xScanGroup.xhscanCommand)
|
||||
register("xsscan", xScanGroup.xsscanCommand)
|
||||
register("xzscan", xScanGroup.xzscanCommand)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ func TestScan(t *testing.T) {
|
|||
testListKeyScan(t, c)
|
||||
testZSetKeyScan(t, c)
|
||||
testSetKeyScan(t, c)
|
||||
|
||||
}
|
||||
|
||||
func checkScanValues(t *testing.T, ay interface{}, values ...interface{}) {
|
||||
|
@ -47,7 +46,7 @@ func checkScanValues(t *testing.T, ay interface{}, values ...interface{}) {
|
|||
|
||||
for i, v := range a {
|
||||
if string(v) != fmt.Sprintf("%v", values[i]) {
|
||||
t.Fatal(fmt.Sprintf("%d %s != %v", string(v), values[i]))
|
||||
t.Fatal(fmt.Sprintf("%d %s != %v", i, string(v), values[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +71,6 @@ func checkScan(t *testing.T, c *goredis.Client, tp string) {
|
|||
} else {
|
||||
checkScanValues(t, ay[1], 5, 6, 7, 8, 9)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testKVScan(t *testing.T, c *goredis.Client) {
|
||||
|
@ -125,7 +123,7 @@ func testSetKeyScan(t *testing.T, c *goredis.Client) {
|
|||
checkScan(t, c, "SET")
|
||||
}
|
||||
|
||||
func TestHashScan(t *testing.T) {
|
||||
func TestXHashScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
|
@ -141,7 +139,23 @@ func TestHashScan(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSetScan(t *testing.T) {
|
||||
func TestHashScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
key := "scan_hash"
|
||||
c.Do("HMSET", key, "a", 1, "b", 2)
|
||||
|
||||
if ay, err := goredis.Values(c.Do("HSCAN", key, "0")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(ay) != 2 {
|
||||
t.Fatal(len(ay))
|
||||
} else {
|
||||
checkScanValues(t, ay[1], "a", 1, "b", 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestXSetScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
|
@ -155,10 +169,25 @@ func TestSetScan(t *testing.T) {
|
|||
} else {
|
||||
checkScanValues(t, ay[1], "a", "b")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestZSetScan(t *testing.T) {
|
||||
func TestSetScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
key := "scan_set"
|
||||
c.Do("SADD", key, "a", "b")
|
||||
|
||||
if ay, err := goredis.Values(c.Do("SSCAN", key, "0")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(ay) != 2 {
|
||||
t.Fatal(len(ay))
|
||||
} else {
|
||||
checkScanValues(t, ay[1], "a", "b")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXZSetScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
|
@ -172,5 +201,21 @@ func TestZSetScan(t *testing.T) {
|
|||
} else {
|
||||
checkScanValues(t, ay[1], "a", 1, "b", 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZSetScan(t *testing.T) {
|
||||
c := getTestConn()
|
||||
defer c.Close()
|
||||
|
||||
key := "scan_zset"
|
||||
c.Do("ZADD", key, 1, "a", 2, "b")
|
||||
|
||||
if ay, err := goredis.Values(c.Do("XZSCAN", key, "0")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(ay) != 2 {
|
||||
t.Fatal(len(ay))
|
||||
} else {
|
||||
checkScanValues(t, ay[1], "a", 1, "b", 2)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue