diff --git a/doc/commands.json b/doc/commands.json index ede7cab..817f252 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -526,5 +526,41 @@ "arguments": "-", "group": "Transaction", "readonly": false + }, + + "SCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "KV", + "readonly": false + }, + + "HSCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "Hash", + "readonly": false + }, + + "LSCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "List", + "readonly": false + }, + + "SSCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "Set", + "readonly": false + }, + + "ZSCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "ZSet", + "readonly": false + }, + + "BSCAN": { + "arguments": "key [MATCH match] [COUNT count]", + "group": "Bitmap", + "readonly": false } } diff --git a/doc/commands.md b/doc/commands.md index 66c227b..d7f1fab 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -26,6 +26,7 @@ Table of Contents - [EXPIREAT key timestamp](#expireat-key-timestamp) - [TTL key](#ttl-key) - [PERSIST key](#persist-key) + - [SCAN key [MATCH match] [COUNT count]](#scan-key-match-match-count-count) - [Hash](#hash) - [HDEL key field [field ...]](#hdel-key-field-field-) - [HEXISTS key field](#hexists-key-field) @@ -44,6 +45,7 @@ Table of Contents - [HEXPIREAT key timestamp](#hexpireat-key-timestamp) - [HTTL key](#httl-key) - [HPERSIST key](#hpersist-key) + - [HSCAN key [MATCH match] [COUNT count]](#hscan-key-match-match-count-count) - [List](#list) - [LINDEX key index](#lindex-key-index) - [LLEN key](#llen-key) @@ -58,6 +60,7 @@ Table of Contents - [LEXPIREAT key timestamp](#lexpireat-key-timestamp) - [LTTL key](#lttl-key) - [LPERSIST key](#lpersist-key) + - [LSCAN key [MATCH match] [COUNT count]](#lscan-key-match-match-count-count) - [Set](#set) - [SADD key member [member ...]](#sadd-key-member-member-) - [SCARD key](#scard-key) @@ -76,7 +79,7 @@ Table of Contents - [SEXPIREAT key timestamp](#sexpireat-key-timestamp) - [STTL key](#sttl-key) - [SPERSIST key](#spersist-key) - + - [SSCAN key [MATCH match] [COUNT count]](#sscan-key-match-match-count-count) - [ZSet](#zset) - [ZADD key score member [score member ...]](#zadd-key-score-member-score-member-) - [ZCARD key](#zcard-key) @@ -102,9 +105,8 @@ Table of Contents ](#zunionstore-destination-numkeys-key-key--weights-weight-weight--aggregate-summinmax) - [ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] ](#zinterstore-destination-numkeys-key-key--weights-weight-weight--aggregate-summinmax) - + - [ZSCAN key [MATCH match] [COUNT count]](#zscan-key-match-match-count-count) - [Bitmap](#bitmap) - - [BGET key](#bget-key) - [BGETBIT key offset](#bgetbit-key-offset) - [BSETBIT key offset value](#bsetbit-key-offset-value) @@ -115,7 +117,7 @@ Table of Contents - [BEXPIREAT key timestamp](#bexpireat-key-timestamp) - [BTTL key](#bttl-key) - [BPERSIST key](#bpersist-key) - + - [BSCAN key [MATCH match] [COUNT count]](#bscan-key-match-match-count-count) - [Replication](#replication) - [SLAVEOF host port](#slaveof-host-port) - [FULLSYNC](#fullsync) @@ -459,6 +461,43 @@ ledis> TTL mykey (integer) -1 ``` +### SCAN key [MATCH match] [COUNT count] + +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>scan "" +1) "" +2) ["a" "b" "c"] +ledis>scan "" count 1 +1) "a" +2) ["a"] +ledis>scan "a" count 1 +1) "b" +2) ["b"] +ledis>scan "b" count 1 +1) "c" +2) ["c"] +ledis>scan "c" count 1 +1) "" +2) [] +``` ## Hash @@ -823,6 +862,11 @@ ledis> HPERSIST not_exists_key (integer) 0 ``` +### HSCAN key [MATCH match] [COUNT count] + +Iterate Hash keys incrementally. + +See `SCAN` for more information. ## List @@ -1115,6 +1159,12 @@ ledis> LPERSIST b (integer) 0 ``` +### LSCAN key [MATCH match] [COUNT count] + +Iterate list keys incrementally. + +See `SCAN` for more information. + ## Set @@ -1537,6 +1587,13 @@ ledis> STTL key (integer) -1 ``` +### SSCAN key [MATCH match] [COUNT count] + +Iterate Set keys incrementally. + +See `SCAN` for more information. + + ## ZSet ### ZADD key score member [score member ...] @@ -2156,6 +2213,12 @@ ledis> ZRANGE out 0 -1 WITHSCORES 4) "10" ``` +### ZSCAN key [MATCH match] [COUNT count] + +Iterate ZSet keys incrementally. + +See `SCAN` for more information. + ## Bitmap @@ -2316,6 +2379,13 @@ ledis> BCOUNT flag 5 6 (refer to [PERSIST](#persist-key) api for other types) +### BSCAN key [MATCH match] [COUNT count] + +Iterate Bitmap keys incrementally. + +See `SCAN` for more information. + + ## Replication ### SLAVEOF host port diff --git a/server/cmd_bit.go b/server/cmd_bit.go index 39762e9..5b23732 100644 --- a/server/cmd_bit.go +++ b/server/cmd_bit.go @@ -273,19 +273,19 @@ func bpersistCommand(c *client) error { } func bscanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.BScan(key, count, inclusive, match); err != nil { + 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] = "" + data[0] = []byte("") } else { - data[0] = append([]byte{'('}, ay[len(ay)-1]...) + data[0] = ay[len(ay)-1] } data[1] = ay c.resp.writeArray(data) diff --git a/server/cmd_hash.go b/server/cmd_hash.go index f292e4b..ed7e458 100644 --- a/server/cmd_hash.go +++ b/server/cmd_hash.go @@ -293,19 +293,19 @@ func hpersistCommand(c *client) error { } func hscanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.HScan(key, count, inclusive, match); err != nil { + 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] = "" + data[0] = []byte("") } else { - data[0] = append([]byte{'('}, ay[len(ay)-1]...) + data[0] = ay[len(ay)-1] } data[1] = ay c.resp.writeArray(data) diff --git a/server/cmd_kv.go b/server/cmd_kv.go index 44fd28a..0be6abf 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -275,10 +275,9 @@ func persistCommand(c *client) error { return nil } -func parseScanArgs(c *client) (key []byte, inclusive bool, match string, count int, err error) { +func parseScanArgs(c *client) (key []byte, match string, count int, err error) { args := c.args count = 10 - inclusive = false switch len(args) { case 0: @@ -287,14 +286,6 @@ func parseScanArgs(c *client) (key []byte, inclusive bool, match string, count i case 1, 3, 5: key = args[0] break - case 2, 4, 6: - key = args[0] - if strings.ToLower(ledis.String(args[len(args)-1])) != "inclusive" { - err = ErrCmdParams - return - } - inclusive = true - args = args[0 : len(args)-1] default: err = ErrCmdParams return @@ -330,12 +321,12 @@ func parseScanArgs(c *client) (key []byte, inclusive bool, match string, count i } func scanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.Scan(key, count, inclusive, match); err != nil { + if ay, err := c.db.Scan(key, count, false, match); err != nil { return err } else { data := make([]interface{}, 2) diff --git a/server/cmd_list.go b/server/cmd_list.go index bd4cc6f..c6cb923 100644 --- a/server/cmd_list.go +++ b/server/cmd_list.go @@ -229,19 +229,19 @@ func lpersistCommand(c *client) error { } func lscanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.LScan(key, count, inclusive, match); err != nil { + 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] = "" + data[0] = []byte("") } else { - data[0] = append([]byte{'('}, ay[len(ay)-1]...) + data[0] = ay[len(ay)-1] } data[1] = ay c.resp.writeArray(data) diff --git a/server/cmd_set.go b/server/cmd_set.go index 052389d..466413d 100644 --- a/server/cmd_set.go +++ b/server/cmd_set.go @@ -263,19 +263,19 @@ func spersistCommand(c *client) error { } func sscanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.SScan(key, count, inclusive, match); err != nil { + 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] = "" + data[0] = []byte("") } else { - data[0] = append([]byte{'('}, ay[len(ay)-1]...) + data[0] = ay[len(ay)-1] } data[1] = ay c.resp.writeArray(data) diff --git a/server/cmd_zset.go b/server/cmd_zset.go index 162c411..6f33f54 100644 --- a/server/cmd_zset.go +++ b/server/cmd_zset.go @@ -639,19 +639,19 @@ func zinterstoreCommand(c *client) error { } func zscanCommand(c *client) error { - key, inclusive, match, count, err := parseScanArgs(c) + key, match, count, err := parseScanArgs(c) if err != nil { return err } - if ay, err := c.db.ZScan(key, count, inclusive, match); err != nil { + 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] = "" + data[0] = []byte("") } else { - data[0] = append([]byte{'('}, ay[len(ay)-1]...) + data[0] = ay[len(ay)-1] } data[1] = ay c.resp.writeArray(data) diff --git a/server/scan_test.go b/server/scan_test.go index e2c671f..809eb39 100644 --- a/server/scan_test.go +++ b/server/scan_test.go @@ -29,6 +29,12 @@ func TestScan(t *testing.T) { defer c.Close() testKVScan(t, c) + testHashScan(t, c) + testListScan(t, c) + testZSetScan(t, c) + testSetScan(t, c) + testBitScan(t, c) + } func checkScanValues(t *testing.T, ay interface{}, values ...int) { @@ -48,14 +54,8 @@ func checkScanValues(t *testing.T, ay interface{}, values ...int) { } } -func testKVScan(t *testing.T, c *ledis.Client) { - for i := 0; i < 10; i++ { - if _, err := c.Do("set", fmt.Sprintf("%d", i), []byte("value")); err != nil { - t.Fatal(err) - } - } - - if ay, err := ledis.Values(c.Do("scan", "", "count", 5)); err != nil { +func checkScan(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)) @@ -65,7 +65,7 @@ func testKVScan(t *testing.T, c *ledis.Client) { checkScanValues(t, ay[1], 0, 1, 2, 3, 4) } - if ay, err := ledis.Values(c.Do("scan", "4", "count", 6)); err != nil { + if ay, err := ledis.Values(c.Do(cmd, "4", "count", 6)); err != nil { t.Fatal(err) } else if len(ay) != 2 { t.Fatal(len(ay)) @@ -75,14 +75,64 @@ func testKVScan(t *testing.T, c *ledis.Client) { checkScanValues(t, ay[1], 5, 6, 7, 8, 9) } - if ay, err := ledis.Values(c.Do("scan", "4", "count", 6, "inclusive")); err != nil { - t.Fatal(err) - } else if len(ay) != 2 { - t.Fatal(len(ay)) - } else if n := ay[0].([]byte); string(n) != "9" { - t.Fatal(string(n)) - } else { - checkScanValues(t, ay[1], 4, 5, 6, 7, 8, 9) +} + +func testKVScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("set", fmt.Sprintf("%d", i), []byte("value")); err != nil { + t.Fatal(err) + } } + checkScan(t, c, "scan") +} + +func testHashScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("hset", fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), []byte("value")); err != nil { + t.Fatal(err) + } + } + + checkScan(t, c, "hscan") +} + +func testListScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("lpush", fmt.Sprintf("%d", i), fmt.Sprintf("%d", i)); err != nil { + t.Fatal(err) + } + } + + checkScan(t, c, "lscan") +} + +func testZSetScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("zadd", fmt.Sprintf("%d", i), i, []byte("value")); err != nil { + t.Fatal(err) + } + } + + checkScan(t, c, "zscan") +} + +func testSetScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("sadd", fmt.Sprintf("%d", i), fmt.Sprintf("%d", i)); err != nil { + t.Fatal(err) + } + } + + checkScan(t, c, "sscan") +} + +func testBitScan(t *testing.T, c *ledis.Client) { + for i := 0; i < 10; i++ { + if _, err := c.Do("bsetbit", fmt.Sprintf("%d", i), 1024, 1); err != nil { + t.Fatal(err) + } + } + + checkScan(t, c, "bscan") }