-
Notifications
You must be signed in to change notification settings - Fork 581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add scan command #199
add scan command #199
Changes from 1 commit
3755e04
a1617b3
24e3509
7ada186
8039246
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -412,6 +412,91 @@ func execCopy(mdb *Server, conn redis.Connection, args [][]byte) redis.Reply { | |
return protocol.MakeIntReply(1) | ||
} | ||
|
||
// execScan iteratively output all keys in the current db | ||
func execScan(db *DB, args [][]byte) redis.Reply { | ||
argsNum := len(args) | ||
if argsNum < 3 && argsNum > 5 { | ||
return protocol.MakeArgNumErrReply("scan") | ||
} | ||
|
||
if argsNum == 1 { | ||
return scanWithArg(db, args, false) | ||
} else if argsNum == 3 { | ||
firstArg := strings.ToLower(string(args[1])) | ||
if firstArg == "match" { | ||
return scanWithArg(db, args, true) | ||
} else if firstArg == "count" { | ||
return scanWithArg(db, args, false) | ||
} else { | ||
return protocol.MakeSyntaxErrReply() | ||
} | ||
} else if argsNum == 5 { | ||
return scanWithArg(db, args, true) | ||
} | ||
return protocol.MakeNullBulkReply() | ||
} | ||
|
||
func scanWithArg(db *DB, args [][]byte, match bool) redis.Reply { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 函数签名改成: execScan0(cursor, pattern, count, typ) |
||
result := make([]redis.Reply, 2) | ||
var m [][]byte | ||
var scanReturnKeys []string | ||
var nextCursor int | ||
cursor, err := strconv.Atoi(string(args[0])) | ||
if err != nil { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
|
||
if len(args) == 1 { | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, 10, "*") | ||
} else if !match { | ||
count, err := strconv.Atoi(string(args[2])) | ||
if err != nil || count < 0 { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, count, "*") | ||
} else { | ||
commandAndArgs := parseScanCommandArgs(args) | ||
numOfcommandAndArgs := len(commandAndArgs) | ||
if numOfcommandAndArgs == 1 { | ||
//if there is only the match parameter, then count defaults to 10 | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, 10, commandAndArgs["match"]) | ||
} else if numOfcommandAndArgs == 2 { | ||
count, err := strconv.Atoi(commandAndArgs["count"]) | ||
if err != nil || count <= 0 { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, count, commandAndArgs["match"]) | ||
} else { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
} | ||
nextCursorTobyte := strconv.FormatInt(int64(nextCursor), 10) | ||
result[0] = protocol.MakeBulkReply([]byte(nextCursorTobyte)) | ||
for _, s := range scanReturnKeys { | ||
if s != "" { | ||
m = append(m, []byte(s)) | ||
} | ||
} | ||
result[1] = protocol.MakeMultiBulkReply(m) | ||
return protocol.MakeMultiRawReply(result) | ||
} | ||
|
||
// parseScanCommandArgs parse the parameters of the scan args | ||
func parseScanCommandArgs(args [][]byte) map[string]string { | ||
arg := make(map[string]string) | ||
argNum := len(args) | ||
if argNum == 3 { | ||
firstCommand := strings.ToLower(string(args[1])) | ||
arg[firstCommand] = string(args[2]) | ||
} else if argNum == 5 { | ||
firstCommand := strings.ToLower(string(args[1])) | ||
arg[firstCommand] = string(args[2]) | ||
secondCommand := strings.ToLower(string(args[3])) | ||
arg[secondCommand] = string(args[4]) | ||
} | ||
return arg | ||
} | ||
|
||
func init() { | ||
registerCommand("Del", execDel, writeAllKeys, undoDel, -2, flagWrite). | ||
attachCommandExtra([]string{redisFlagWrite}, 1, -1, 1) | ||
|
@@ -443,4 +528,6 @@ func init() { | |
attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1) | ||
registerCommand("Keys", execKeys, noPrepare, nil, 2, flagReadOnly). | ||
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1) | ||
registerCommand("Scan", execScan, readAllKeys, nil, -2, flagReadOnly). | ||
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package dict | ||
|
||
import ( | ||
"github.com/hdt3213/godis/lib/wildcard" | ||
"math" | ||
"math/rand" | ||
"sort" | ||
|
@@ -435,3 +436,56 @@ func (dict *ConcurrentDict) RWUnLocks(writeKeys []string, readKeys []string) { | |
} | ||
} | ||
} | ||
|
||
// ScanKeys iteratively output all keys in the current db | ||
func (dict *ConcurrentDict) ScanKeys(cursor, count int, matchKey string) ([]string, int) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. matchKey -> pattern |
||
nextCursor := 0 | ||
size := dict.Len() | ||
result := make([]string, count) | ||
r := make(map[string]struct{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不要使用过于简单的变量名 |
||
if count >= size { | ||
return dict.Keys(), nextCursor | ||
} | ||
remainingKeys := dict.table[cursor:] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. table 中取出来的是 shard, 为啥起名叫 remainingKeys? |
||
i := 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 直接用 len(r) 不就行了 |
||
for tableK, s := range remainingKeys { | ||
s.mutex.RLock() | ||
f := func(m map[string]struct{}) bool { | ||
defer s.mutex.RUnlock() | ||
for key, _ := range s.m { | ||
if key != "" { | ||
if matchKey != "*" { | ||
pattern, err := wildcard.CompilePattern(matchKey) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CompilePattern 执行一次就可以了。。 |
||
if err != nil { | ||
return false | ||
} | ||
if pattern.IsMatch(key) { | ||
m[key] = struct{}{} | ||
i++ | ||
} | ||
} else { | ||
m[key] = struct{}{} | ||
i++ | ||
} | ||
} | ||
if count <= i { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scan 命令不保证返回的元素数一定等于 count 参数,但是保证从遍历开始直到完整遍历结束期间, 一直存在于数据集内的所有元素都会被返回。如果到了 count 就中止的话,当前shard 中可能有一些 key 在遍历期间一直存在但因为遍历中止未被返回。 所以应完整遍历shard, 当 len(result) >= count 后返回即可。
|
||
return false | ||
} | ||
} | ||
return true | ||
} | ||
nextCursor = tableK + cursor + 1 | ||
if !f(r) { | ||
break | ||
} | ||
} | ||
j := 0 | ||
for k := range r { | ||
result[j] = k | ||
j++ | ||
} | ||
if nextCursor >= dict.shardCount { | ||
nextCursor = 0 | ||
} | ||
return result, nextCursor | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scan 的参数有 MATCH,COUNT, TYPE 3个,实际执行 scan 的函数签名应该是 scan0(cursor, pattern, count, typ)。
现在的解析命令行逻辑过于晦涩, 可以参考一下 execSet 的实现