mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 16:57:06 +08:00
Add sscan, hscan, zscan
This commit is contained in:
@@ -496,6 +496,56 @@ func execHRandField(db *DB, args [][]byte) redis.Reply {
|
|||||||
return &protocol.EmptyMultiBulkReply{}
|
return &protocol.EmptyMultiBulkReply{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execHScan(db *DB, args [][]byte) redis.Reply {
|
||||||
|
var count int = 10
|
||||||
|
var pattern string = "*"
|
||||||
|
if len(args) > 2 {
|
||||||
|
for i := 2; i < len(args); i++ {
|
||||||
|
arg := strings.ToLower(string(args[i]))
|
||||||
|
if arg == "count" {
|
||||||
|
count0, err := strconv.Atoi(string(args[i+1]))
|
||||||
|
if err != nil {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
count = count0
|
||||||
|
i++
|
||||||
|
} else if arg == "match" {
|
||||||
|
pattern = string(args[i+1])
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args) < 2 {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
key := string(args[0])
|
||||||
|
// get entity
|
||||||
|
dict, errReply := db.getAsDict(key)
|
||||||
|
if errReply != nil {
|
||||||
|
return errReply
|
||||||
|
}
|
||||||
|
if dict == nil {
|
||||||
|
return &protocol.NullBulkReply{}
|
||||||
|
}
|
||||||
|
cursor, err := strconv.Atoi(string(args[1]))
|
||||||
|
if err != nil {
|
||||||
|
return protocol.MakeErrReply("ERR invalid cursor")
|
||||||
|
}
|
||||||
|
|
||||||
|
keysReply, nextCursor := dict.DictScan(cursor, count, pattern)
|
||||||
|
if nextCursor < 0 {
|
||||||
|
return protocol.MakeErrReply("Invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]redis.Reply, 2)
|
||||||
|
result[0] = protocol.MakeBulkReply([]byte(strconv.FormatInt(int64(nextCursor), 10)))
|
||||||
|
result[1] = protocol.MakeMultiBulkReply(keysReply)
|
||||||
|
|
||||||
|
return protocol.MakeMultiRawReply(result)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerCommand("HSet", execHSet, writeFirstKey, undoHSet, 4, flagWrite).
|
registerCommand("HSet", execHSet, writeFirstKey, undoHSet, 4, flagWrite).
|
||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
||||||
@@ -529,4 +579,6 @@ func init() {
|
|||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
||||||
registerCommand("HRandField", execHRandField, readFirstKey, nil, -2, flagReadOnly).
|
registerCommand("HRandField", execHRandField, readFirstKey, nil, -2, flagReadOnly).
|
||||||
attachCommandExtra([]string{redisFlagRandom, redisFlagReadonly}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagRandom, redisFlagReadonly}, 1, 1, 1)
|
||||||
|
registerCommand("HScan", execHScan, readFirstKey, nil, -2, flagReadOnly).
|
||||||
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
||||||
}
|
}
|
||||||
|
@@ -346,3 +346,48 @@ func TestUndoHIncr(t *testing.T) {
|
|||||||
result := testDB.Exec(nil, utils.ToCmdLine("hget", key, field))
|
result := testDB.Exec(nil, utils.ToCmdLine("hget", key, field))
|
||||||
asserts.AssertBulkReply(t, result, "1")
|
asserts.AssertBulkReply(t, result, "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHScan(t *testing.T) {
|
||||||
|
testDB.Flush()
|
||||||
|
hashKey := "test:hash"
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
value := key
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("hset", hashKey, "a"+key, value))
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
value := key
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("hset", hashKey, "b"+key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := testDB.Exec(nil, utils.ToCmdLine("hscan", hashKey, "0", "count", "10"))
|
||||||
|
cursorStr := string(result.(*protocol.MultiRawReply).Replies[0].(*protocol.BulkReply).Arg)
|
||||||
|
cursor, err := strconv.Atoi(cursorStr)
|
||||||
|
if err == nil {
|
||||||
|
if cursor != 0 {
|
||||||
|
t.Errorf("expect cursor 0, actually %d", cursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("get scan result error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test hscan 0 match a*
|
||||||
|
result = testDB.Exec(nil, utils.ToCmdLine("hscan", hashKey, "0", "match", "a*"))
|
||||||
|
returnKeys := result.(*protocol.MultiRawReply).Replies[1].(*protocol.MultiBulkReply).Args
|
||||||
|
i := 0
|
||||||
|
for i < len(returnKeys) {
|
||||||
|
if i%2 != 0 {
|
||||||
|
i++
|
||||||
|
continue // pass value
|
||||||
|
}
|
||||||
|
key := string(returnKeys[i])
|
||||||
|
i++
|
||||||
|
if key[0] != 'a' {
|
||||||
|
t.Errorf("The key %s should match a*", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -505,6 +505,6 @@ func init() {
|
|||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
|
||||||
registerCommand("Keys", execKeys, noPrepare, nil, 2, flagReadOnly).
|
registerCommand("Keys", execKeys, noPrepare, nil, 2, flagReadOnly).
|
||||||
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
||||||
registerCommand("Scan", execScan, readAllKeys, nil, -2, flagReadOnly).
|
registerCommand("Scan", execScan, noPrepare, nil, -2, flagReadOnly).
|
||||||
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/hdt3213/godis/lib/utils"
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"github.com/hdt3213/godis/redis/protocol"
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) getAsSet(key string) (*HashSet.Set, protocol.ErrorReply) {
|
func (db *DB) getAsSet(key string) (*HashSet.Set, protocol.ErrorReply) {
|
||||||
@@ -354,6 +355,53 @@ func execSRandMember(db *DB, args [][]byte) redis.Reply {
|
|||||||
return &protocol.EmptyMultiBulkReply{}
|
return &protocol.EmptyMultiBulkReply{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execSScan(db *DB, args [][]byte) redis.Reply {
|
||||||
|
var count int = 10
|
||||||
|
var pattern string = "*"
|
||||||
|
if len(args) > 2 {
|
||||||
|
for i := 2; i < len(args); i++ {
|
||||||
|
arg := strings.ToLower(string(args[i]))
|
||||||
|
if arg == "count" {
|
||||||
|
count0, err := strconv.Atoi(string(args[i+1]))
|
||||||
|
if err != nil {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
count = count0
|
||||||
|
i++
|
||||||
|
} else if arg == "match" {
|
||||||
|
pattern = string(args[i+1])
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := string(args[0])
|
||||||
|
// get entity
|
||||||
|
set, errReply := db.getAsSet(key)
|
||||||
|
if errReply != nil {
|
||||||
|
return errReply
|
||||||
|
}
|
||||||
|
if set == nil {
|
||||||
|
return &protocol.EmptyMultiBulkReply{}
|
||||||
|
}
|
||||||
|
cursor, err := strconv.Atoi(string(args[1]))
|
||||||
|
if err != nil {
|
||||||
|
return protocol.MakeErrReply("ERR invalid cursor")
|
||||||
|
}
|
||||||
|
|
||||||
|
keysReply, nextCursor := set.SetScan(cursor, count, pattern)
|
||||||
|
if nextCursor < 0 {
|
||||||
|
return protocol.MakeErrReply("Invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]redis.Reply, 2)
|
||||||
|
result[0] = protocol.MakeBulkReply([]byte(strconv.FormatInt(int64(nextCursor), 10)))
|
||||||
|
result[1] = protocol.MakeMultiBulkReply(keysReply)
|
||||||
|
|
||||||
|
return protocol.MakeMultiRawReply(result)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerCommand("SAdd", execSAdd, writeFirstKey, undoSetChange, -3, flagWrite).
|
registerCommand("SAdd", execSAdd, writeFirstKey, undoSetChange, -3, flagWrite).
|
||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
||||||
@@ -381,4 +429,6 @@ func init() {
|
|||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
||||||
registerCommand("SRandMember", execSRandMember, readFirstKey, nil, -2, flagReadOnly).
|
registerCommand("SRandMember", execSRandMember, readFirstKey, nil, -2, flagReadOnly).
|
||||||
attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom}, 1, 1, 1)
|
||||||
|
registerCommand("SScan", execSScan, readFirstKey, nil, -2, flagReadOnly).
|
||||||
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagSortForScript}, 1, 1, 1)
|
||||||
}
|
}
|
||||||
|
@@ -248,3 +248,40 @@ func TestSRandMember(t *testing.T) {
|
|||||||
result = testDB.Exec(nil, utils.ToCmdLine("SRandMember", key, "-110"))
|
result = testDB.Exec(nil, utils.ToCmdLine("SRandMember", key, "-110"))
|
||||||
asserts.AssertMultiBulkReplySize(t, result, 110)
|
asserts.AssertMultiBulkReplySize(t, result, 110)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSScan(t *testing.T) {
|
||||||
|
testDB.Flush()
|
||||||
|
setKey := "test:set"
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("sadd", setKey, "a"+key))
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("sadd", setKey, "b"+key))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := testDB.Exec(nil, utils.ToCmdLine("sscan", setKey, "0", "count", "10"))
|
||||||
|
cursorStr := string(result.(*protocol.MultiRawReply).Replies[0].(*protocol.BulkReply).Arg)
|
||||||
|
cursor, err := strconv.Atoi(cursorStr)
|
||||||
|
if err == nil {
|
||||||
|
if cursor != 0 {
|
||||||
|
t.Errorf("expect cursor 0, actually %d", cursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("get scan result error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test sscan 0 match a*
|
||||||
|
result = testDB.Exec(nil, utils.ToCmdLine("sscan", setKey, "0", "match", "a*"))
|
||||||
|
returnKeys := result.(*protocol.MultiRawReply).Replies[1].(*protocol.MultiBulkReply).Args
|
||||||
|
for i := range returnKeys {
|
||||||
|
key := string(returnKeys[i])
|
||||||
|
if key[0] != 'a' {
|
||||||
|
t.Errorf("The key %s should match a*", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
SortedSet "github.com/hdt3213/godis/datastruct/sortedset"
|
SortedSet "github.com/hdt3213/godis/datastruct/sortedset"
|
||||||
"github.com/hdt3213/godis/interface/database"
|
"github.com/hdt3213/godis/interface/database"
|
||||||
"github.com/hdt3213/godis/interface/redis"
|
"github.com/hdt3213/godis/interface/redis"
|
||||||
"github.com/hdt3213/godis/lib/utils"
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"github.com/hdt3213/godis/redis/protocol"
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) getAsSortedSet(key string) (*SortedSet.SortedSet, protocol.ErrorReply) {
|
func (db *DB) getAsSortedSet(key string) (*SortedSet.SortedSet, protocol.ErrorReply) {
|
||||||
@@ -796,6 +795,53 @@ func execZRevRangeByLex(db *DB, args [][]byte) redis.Reply {
|
|||||||
return protocol.MakeMultiBulkReply(result)
|
return protocol.MakeMultiBulkReply(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execZScan(db *DB, args [][]byte) redis.Reply {
|
||||||
|
var count int = 10
|
||||||
|
var pattern string = "*"
|
||||||
|
if len(args) > 2 {
|
||||||
|
for i := 2; i < len(args); i++ {
|
||||||
|
arg := strings.ToLower(string(args[i]))
|
||||||
|
if arg == "count" {
|
||||||
|
count0, err := strconv.Atoi(string(args[i+1]))
|
||||||
|
if err != nil {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
count = count0
|
||||||
|
i++
|
||||||
|
} else if arg == "match" {
|
||||||
|
pattern = string(args[i+1])
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
return &protocol.SyntaxErrReply{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := string(args[0])
|
||||||
|
// get entity
|
||||||
|
set, errReply := db.getAsSortedSet(key)
|
||||||
|
if errReply != nil {
|
||||||
|
return errReply
|
||||||
|
}
|
||||||
|
if set == nil {
|
||||||
|
return &protocol.EmptyMultiBulkReply{}
|
||||||
|
}
|
||||||
|
cursor, err := strconv.Atoi(string(args[1]))
|
||||||
|
if err != nil {
|
||||||
|
return protocol.MakeErrReply("ERR invalid cursor")
|
||||||
|
}
|
||||||
|
|
||||||
|
keysReply, nextCursor := set.ZSetScan(cursor, count, pattern)
|
||||||
|
if nextCursor < 0 {
|
||||||
|
return protocol.MakeErrReply("Invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]redis.Reply, 2)
|
||||||
|
result[0] = protocol.MakeBulkReply([]byte(strconv.FormatInt(int64(nextCursor), 10)))
|
||||||
|
result[1] = protocol.MakeMultiBulkReply(keysReply)
|
||||||
|
|
||||||
|
return protocol.MakeMultiRawReply(result)
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerCommand("ZAdd", execZAdd, writeFirstKey, undoZAdd, -4, flagWrite).
|
registerCommand("ZAdd", execZAdd, writeFirstKey, undoZAdd, -4, flagWrite).
|
||||||
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
||||||
@@ -835,4 +881,6 @@ func init() {
|
|||||||
attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
|
||||||
registerCommand("ZRevRangeByLex", execZRevRangeByLex, readFirstKey, nil, -4, flagReadOnly).
|
registerCommand("ZRevRangeByLex", execZRevRangeByLex, readFirstKey, nil, -4, flagReadOnly).
|
||||||
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
||||||
|
registerCommand("ZScan", execZScan, readFirstKey, nil, -2, flagReadOnly).
|
||||||
|
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
|
"github.com/hdt3213/godis/redis/protocol/asserts"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hdt3213/godis/lib/utils"
|
|
||||||
"github.com/hdt3213/godis/redis/protocol/asserts"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestZAdd(t *testing.T) {
|
func TestZAdd(t *testing.T) {
|
||||||
@@ -762,3 +762,49 @@ func TestZRevRangeByLex(t *testing.T) {
|
|||||||
result30 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "+", "-", "limit", "2", "2"))
|
result30 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "+", "-", "limit", "2", "2"))
|
||||||
asserts.AssertMultiBulkReply(t, result30, []string{"c", "b"})
|
asserts.AssertMultiBulkReply(t, result30, []string{"c", "b"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestZScan(t *testing.T) {
|
||||||
|
testDB.Flush()
|
||||||
|
zsetKey := "zsetkey"
|
||||||
|
expectKeyScore := make(map[string]float64)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
expectKeyScore[key] = float64(i)
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("zadd", zsetKey, strconv.FormatInt(int64(i), 10), "a"+key))
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
key := string(rune(i))
|
||||||
|
expectKeyScore[key] = float64(i + 3)
|
||||||
|
testDB.Exec(nil, utils.ToCmdLine("zadd", zsetKey, strconv.FormatInt(int64(i), 10), "b"+key))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := testDB.Exec(nil, utils.ToCmdLine("zscan", zsetKey, "0", "count", "10"))
|
||||||
|
cursorStr := string(result.(*protocol.MultiRawReply).Replies[0].(*protocol.BulkReply).Arg)
|
||||||
|
cursor, err := strconv.Atoi(cursorStr)
|
||||||
|
if err == nil {
|
||||||
|
if cursor != 0 {
|
||||||
|
t.Errorf("expect cursor 0, actually %d", cursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Errorf("get scan result error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// test zscan 0 match a*
|
||||||
|
result = testDB.Exec(nil, utils.ToCmdLine("zscan", zsetKey, "0", "match", "a*"))
|
||||||
|
returnKeys := result.(*protocol.MultiRawReply).Replies[1].(*protocol.MultiBulkReply).Args
|
||||||
|
i := 0
|
||||||
|
for i < len(returnKeys) {
|
||||||
|
if i%2 != 0 {
|
||||||
|
i++
|
||||||
|
continue // pass score
|
||||||
|
}
|
||||||
|
key := string(returnKeys[i])
|
||||||
|
i++
|
||||||
|
if key[0] != 'a' {
|
||||||
|
t.Errorf("The key %s should match a*", key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
package dict
|
package dict
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hdt3213/godis/lib/wildcard"
|
||||||
|
)
|
||||||
|
|
||||||
// SimpleDict wraps a map, it is not thread safe
|
// SimpleDict wraps a map, it is not thread safe
|
||||||
type SimpleDict struct {
|
type SimpleDict struct {
|
||||||
m map[string]interface{}
|
m map[string]interface{}
|
||||||
@@ -122,5 +126,20 @@ func (dict *SimpleDict) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dict *SimpleDict) DictScan(cursor int, count int, pattern string) ([][]byte, int) {
|
func (dict *SimpleDict) DictScan(cursor int, count int, pattern string) ([][]byte, int) {
|
||||||
return stringsToBytes(dict.Keys()), 0
|
result := make([][]byte, 0)
|
||||||
|
matchKey, err := wildcard.CompilePattern(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return result, -1
|
||||||
|
}
|
||||||
|
for k := range dict.m {
|
||||||
|
if pattern == "*" || matchKey.IsMatch(k) {
|
||||||
|
raw, exists := dict.Get(k)
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, []byte(k))
|
||||||
|
result = append(result, raw.([]byte))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, 0
|
||||||
}
|
}
|
||||||
|
@@ -53,3 +53,30 @@ func TestSimpleDict_PutIfExists(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimpleDict_Scan(t *testing.T) {
|
||||||
|
d := MakeSimple()
|
||||||
|
size := 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "a" + utils.RandString(5)
|
||||||
|
d.Put(str, []byte(str))
|
||||||
|
}
|
||||||
|
keys, nextCursor := d.DictScan(0, size, "*")
|
||||||
|
if len(keys) != size*2 {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size*2, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextCursor != 0 {
|
||||||
|
t.Errorf("expect 0, actual: %d", nextCursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "b" + utils.RandString(5)
|
||||||
|
d.Put(str, str)
|
||||||
|
}
|
||||||
|
keys, _ = d.DictScan(0, size*2, "a*")
|
||||||
|
if len(keys) != size*2 {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size*2, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package set
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hdt3213/godis/datastruct/dict"
|
"github.com/hdt3213/godis/datastruct/dict"
|
||||||
|
"github.com/hdt3213/godis/lib/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set is a set of elements based on hash table
|
// Set is a set of elements based on hash table
|
||||||
@@ -149,3 +150,20 @@ func (set *Set) RandomMembers(limit int) []string {
|
|||||||
func (set *Set) RandomDistinctMembers(limit int) []string {
|
func (set *Set) RandomDistinctMembers(limit int) []string {
|
||||||
return set.dict.RandomDistinctKeys(limit)
|
return set.dict.RandomDistinctKeys(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan set with cursor and pattern
|
||||||
|
func (set *Set) SetScan(cursor int, count int, pattern string) ([][]byte, int) {
|
||||||
|
result := make([][]byte, 0)
|
||||||
|
matchKey, err := wildcard.CompilePattern(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return result, -1
|
||||||
|
}
|
||||||
|
set.ForEach(func(member string) bool {
|
||||||
|
if pattern == "*" || matchKey.IsMatch(member) {
|
||||||
|
result = append(result, []byte(member))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return result, 0
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package set
|
package set
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -30,3 +31,30 @@ func TestSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetScan(t *testing.T) {
|
||||||
|
set := Make()
|
||||||
|
size := 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "a" + utils.RandString(5)
|
||||||
|
set.Add(str)
|
||||||
|
}
|
||||||
|
keys, nextCursor := set.SetScan(0, size, "*")
|
||||||
|
if len(keys) != size {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextCursor != 0 {
|
||||||
|
t.Errorf("expect 0, actual: %d", nextCursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "b" + utils.RandString(5)
|
||||||
|
set.Add(str)
|
||||||
|
}
|
||||||
|
keys, _ = set.SetScan(0, size*2, "a*")
|
||||||
|
if len(keys) != size {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package sortedset
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hdt3213/godis/lib/wildcard"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SortedSet is a set which keys sorted by bound score
|
// SortedSet is a set which keys sorted by bound score
|
||||||
@@ -236,3 +238,22 @@ func (sortedSet *SortedSet) RemoveByRank(start int64, stop int64) int64 {
|
|||||||
}
|
}
|
||||||
return int64(len(removed))
|
return int64(len(removed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sortedSet *SortedSet) ZSetScan(cursor int, count int, pattern string) ([][]byte, int) {
|
||||||
|
result := make([][]byte, 0)
|
||||||
|
matchKey, err := wildcard.CompilePattern(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return result, -1
|
||||||
|
}
|
||||||
|
for k := range sortedSet.dict {
|
||||||
|
if pattern == "*" || matchKey.IsMatch(k) {
|
||||||
|
elem, exists := sortedSet.dict[k]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, []byte(k))
|
||||||
|
result = append(result, []byte(strconv.FormatFloat(elem.Score, 'f', 10, 64)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, 0
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package sortedset
|
package sortedset
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
|
)
|
||||||
|
|
||||||
func TestSortedSet_PopMin(t *testing.T) {
|
func TestSortedSet_PopMin(t *testing.T) {
|
||||||
var set = Make()
|
var set = Make()
|
||||||
@@ -14,3 +18,30 @@ func TestSortedSet_PopMin(t *testing.T) {
|
|||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetScan(t *testing.T) {
|
||||||
|
set := Make()
|
||||||
|
size := 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "a" + utils.RandString(5)
|
||||||
|
set.Add(str, float64(i))
|
||||||
|
}
|
||||||
|
keys, nextCursor := set.ZSetScan(0, size, "*")
|
||||||
|
if len(keys) != size*2 {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size*2, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextCursor != 0 {
|
||||||
|
t.Errorf("expect 0, actual: %d", nextCursor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
str := "b" + utils.RandString(5)
|
||||||
|
set.Add(str, float64(i+size))
|
||||||
|
}
|
||||||
|
keys, _ = set.ZSetScan(0, size*2, "a*")
|
||||||
|
if len(keys) != size*2 {
|
||||||
|
t.Errorf("expect %d keys, actual: %d", size*2, len(keys))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user