-
Notifications
You must be signed in to change notification settings - Fork 576
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 3 commits
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,146 @@ func execCopy(mdb *Server, conn redis.Connection, args [][]byte) redis.Reply { | |
return protocol.MakeIntReply(1) | ||
} | ||
|
||
//// execScan1 iteratively output all keys in the current db | ||
//func execScan1(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() | ||
//} | ||
|
||
// execScan iteratively output all keys in the current db | ||
func execScan(db *DB, args [][]byte) redis.Reply { | ||
const ( | ||
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.
这样不就完事了,为啥写这么麻烦 |
||
noArgs = iota | ||
count | ||
match | ||
countAndMatch = 5 | ||
) | ||
commandArg := noArgs | ||
argsNum := len(args) | ||
if argsNum > 5 { | ||
return protocol.MakeArgNumErrReply("scan") | ||
} | ||
if argsNum == 1 { | ||
commandArg = noArgs | ||
} else if argsNum > 2 { | ||
for i := 1; i < argsNum; i++ { | ||
arg := strings.ToLower(string(args[i])) | ||
if arg == "count" { | ||
commandArg = count | ||
} else if arg == "match" { | ||
commandArg = match | ||
} else if i == 4 { | ||
if string(args[1]) == "count" && string(args[3]) == "match" || | ||
(string(args[1]) == "match" && string(args[3]) == "count") { | ||
commandArg = countAndMatch | ||
} else { | ||
return protocol.MakeSyntaxErrReply() | ||
} | ||
break | ||
} else { | ||
i++ | ||
} | ||
} | ||
} | ||
switch commandArg { | ||
case noArgs: | ||
return execScanWithArg(db, args, noArgs) | ||
case count: | ||
return execScanWithArg(db, args, count) | ||
case match: | ||
return execScanWithArg(db, args, match) | ||
case countAndMatch: | ||
return execScanWithArg(db, args, countAndMatch) | ||
} | ||
return protocol.MakeNullBulkReply() | ||
} | ||
|
||
// execScanWithArg execute scan command based on cli args | ||
func execScanWithArg(db *DB, args [][]byte, argType int) 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 string, pattern string, count 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. 上次说的 type 不是参数类型, scan 命令可以根据 string/list/hash/set/sortedset 类型进行过滤. |
||
result := make([]redis.Reply, 2) | ||
var multiBulkReply [][]byte | ||
var scanReturnKeys []string | ||
var nextCursor int | ||
cursor, err := strconv.Atoi(string(args[0])) | ||
if err != nil { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
// no args | ||
if argType == 0 { | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, 10, "*") | ||
} else if argType >= 1 { | ||
commandAndArgs := parseScanCommandArgs(args) | ||
// only count | ||
if argType == 1 { | ||
count, err := strconv.Atoi(commandAndArgs["count"]) | ||
if err != nil || count < 0 { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, count, "*") | ||
} else if argType == 2 { | ||
// only match | ||
// if there is only the match parameter, then count defaults to 10 | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, 10, commandAndArgs["match"]) | ||
} else if argType == 5 { | ||
// count and match | ||
count, err := strconv.Atoi(commandAndArgs["count"]) | ||
if err != nil || count < 0 { | ||
return &protocol.SyntaxErrReply{} | ||
} | ||
scanReturnKeys, nextCursor = db.data.ScanKeys(cursor, count, commandAndArgs["match"]) | ||
} | ||
} | ||
if nextCursor == -1 { | ||
return protocol.MakeErrReply(scanReturnKeys[0]) | ||
} | ||
nextCursorTobyte := strconv.FormatInt(int64(nextCursor), 10) | ||
result[0] = protocol.MakeBulkReply([]byte(nextCursorTobyte)) | ||
for _, s := range scanReturnKeys { | ||
if s != "" { | ||
multiBulkReply = append(multiBulkReply, []byte(s)) | ||
} | ||
} | ||
|
||
result[1] = protocol.MakeMultiBulkReply(multiBulkReply) | ||
println() | ||
return protocol.MakeMultiRawReply(result) | ||
} | ||
|
||
// parseScanCommandArgs parse the parameters of the scan args | ||
func parseScanCommandArgs(args [][]byte) map[string]string { | ||
// solving the order problem of count and match parameters | ||
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 +583,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,60 @@ func (dict *ConcurrentDict) RWUnLocks(writeKeys []string, readKeys []string) { | |
} | ||
} | ||
} | ||
|
||
// ScanKeys iteratively output all keys in the current db | ||
func (dict *ConcurrentDict) ScanKeys(cursor, count int, pattern string) ([]string, int) { | ||
nextCursor := 0 | ||
errReturnCode := -1 | ||
size := dict.Len() | ||
result := make([]string, count) | ||
storeScanKeysTemp := make(map[string]struct{}) | ||
if pattern == "*" && count >= size { | ||
return dict.Keys(), nextCursor | ||
} | ||
|
||
remainingShards := dict.table[cursor:] | ||
|
||
matchKey, err := wildcard.CompilePattern(pattern) | ||
if err != nil { | ||
return []string{err.Error()}, errReturnCode | ||
} | ||
|
||
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 shardIndex, s := range remainingShards { | ||
s.mutex.RLock() | ||
f := func(m map[string]struct{}) bool { | ||
defer s.mutex.RUnlock() | ||
for key, _ := range s.m { | ||
if key != "" { | ||
if pattern != "*" { | ||
if matchKey.IsMatch(key) { | ||
m[key] = struct{}{} | ||
i++ | ||
} | ||
} else { | ||
m[key] = struct{}{} | ||
i++ | ||
} | ||
} | ||
if len(m) >= count { | ||
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. 遍历完 shard 再返回 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. 这里有个问题没想明白,如果每次都需要把所有的shard遍历完再返回的话 |
||
return false | ||
} | ||
} | ||
return true | ||
} | ||
nextCursor = shardIndex + cursor + 1 | ||
if !f(storeScanKeysTemp) { | ||
break | ||
} | ||
} | ||
j := 0 | ||
for k := range storeScanKeysTemp { | ||
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 的实现