Skip to content

Commit

Permalink
feat: support rdb
Browse files Browse the repository at this point in the history
  • Loading branch information
xgzlucario committed Nov 7, 2024
1 parent 7f230f1 commit 23baf4e
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 52 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ run-gc:

test-cover:
make clean
go test ./... -race -coverprofile=coverage.txt -covermode=atomic
go test . -race -coverprofile=coverage.txt -covermode=atomic
go tool cover -html=coverage.txt -o coverage.html

fuzz-test:
Expand Down
73 changes: 42 additions & 31 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var cmdTable = []*Command{
{"sadd", saddCommand, 2, true},
{"srem", sremCommand, 2, true},
{"spop", spopCommand, 1, true},
{"smembers", smembersCommand, 1, false},
{"zadd", zaddCommand, 3, true},
{"zrem", zremCommand, 2, true},
{"zrank", zrankCommand, 2, false},
Expand All @@ -63,6 +64,7 @@ var cmdTable = []*Command{
{"eval", evalCommand, 2, true},
{"ping", pingCommand, 0, false},
{"flushdb", flushdbCommand, 0, true},
{"load", loadCommand, 0, false},
{"save", saveCommand, 0, false},
}

Expand Down Expand Up @@ -144,11 +146,11 @@ func setCommand(writer *resp.Writer, args []resp.RESP) {
}

func incrCommand(writer *resp.Writer, args []resp.RESP) {
key := args[0].ToStringUnsafe()
key := args[0].ToString()

object, ttl := db.dict.Get(key)
if ttl == KEY_NOT_EXIST {
db.dict.Set(strings.Clone(key), 1)
db.dict.Set(key, 1)
writer.WriteInteger(1)
return
}
Expand All @@ -157,7 +159,7 @@ func incrCommand(writer *resp.Writer, args []resp.RESP) {
case int:
num := v + 1
writer.WriteInteger(num)
db.dict.Set(strings.Clone(key), num)
db.dict.Set(key, num)

case []byte:
// conv to integer
Expand Down Expand Up @@ -203,40 +205,37 @@ func delCommand(writer *resp.Writer, args []resp.RESP) {
}

func hsetCommand(writer *resp.Writer, args []resp.RESP) {
hash := args[0]
key := args[0]
args = args[1:]

if len(args)%2 == 1 {
writer.WriteError(errWrongArguments)
return
}

hmap, err := fetchMap(hash, true)
hmap, err := fetchMap(key, true)
if err != nil {
writer.WriteError(err)
return
}

var count int
for i := 0; i < len(args); i += 2 {
key := args[i].ToString()
field := args[i].ToString()
value := args[i+1].Clone()
if hmap.Set(key, value) {
if hmap.Set(field, value) {
count++
}
}
writer.WriteInteger(count)
}

func hgetCommand(writer *resp.Writer, args []resp.RESP) {
hash := args[0]
key := args[1].ToStringUnsafe()
hmap, err := fetchMap(hash)
key := args[0]
field := args[1].ToStringUnsafe()
hmap, err := fetchMap(key)
if err != nil {
writer.WriteError(errWrongType)
return
}
value, ok := hmap.Get(key)
value, ok := hmap.Get(field)
if ok {
writer.WriteBulk(value)
} else {
Expand All @@ -245,25 +244,25 @@ func hgetCommand(writer *resp.Writer, args []resp.RESP) {
}

func hdelCommand(writer *resp.Writer, args []resp.RESP) {
hash := args[0]
keys := args[1:]
hmap, err := fetchMap(hash)
key := args[0]
fields := args[1:]
hmap, err := fetchMap(key)
if err != nil {
writer.WriteError(err)
return
}
var count int
for _, v := range keys {
if hmap.Remove(v.ToStringUnsafe()) {
for _, field := range fields {
if hmap.Remove(field.ToStringUnsafe()) {
count++
}
}
writer.WriteInteger(count)
}

func hgetallCommand(writer *resp.Writer, args []resp.RESP) {
hash := args[0]
hmap, err := fetchMap(hash)
key := args[0]
hmap, err := fetchMap(key)
if err != nil {
writer.WriteError(err)
return
Expand Down Expand Up @@ -397,6 +396,19 @@ func sremCommand(writer *resp.Writer, args []resp.RESP) {
writer.WriteInteger(count)
}

func smembersCommand(writer *resp.Writer, args []resp.RESP) {
key := args[0]
set, err := fetchSet(key)
if err != nil {
writer.WriteError(err)
return
}
writer.WriteArrayHead(set.Len())
set.Scan(func(key string) {
writer.WriteBulkString(key)
})
}

func spopCommand(writer *resp.Writer, args []resp.RESP) {
key := args[0]
set, err := fetchSet(key)
Expand All @@ -415,13 +427,11 @@ func spopCommand(writer *resp.Writer, args []resp.RESP) {
func zaddCommand(writer *resp.Writer, args []resp.RESP) {
key := args[0]
args = args[1:]

zset, err := fetchZSet(key, true)
if err != nil {
writer.WriteError(err)
return
}

var count int
for i := 0; i < len(args); i += 2 {
score, err := args[i].ToFloat()
Expand All @@ -440,13 +450,11 @@ func zaddCommand(writer *resp.Writer, args []resp.RESP) {
func zrankCommand(writer *resp.Writer, args []resp.RESP) {
key := args[0]
member := args[1].ToStringUnsafe()

zset, err := fetchZSet(key)
if err != nil {
writer.WriteError(err)
return
}

rank, _ := zset.Rank(member)
if rank < 0 {
writer.WriteNull()
Expand Down Expand Up @@ -542,14 +550,17 @@ func flushdbCommand(writer *resp.Writer, _ []resp.RESP) {
writer.WriteSString("OK")
}

func saveCommand(writer *resp.Writer, _ []resp.RESP) {
rdb, err := NewRdb(server.config.SaveFileName)
if err != nil {
func loadCommand(writer *resp.Writer, _ []resp.RESP) {
db.dict = New()
if err := db.rdb.LoadDB(); err != nil {
writer.WriteError(err)
return
}
err = rdb.SaveDB()
if err != nil {
writer.WriteBulkString("OK")
}

func saveCommand(writer *resp.Writer, _ []resp.RESP) {
if err := db.rdb.SaveDB(); err != nil {
writer.WriteError(err)
return
}
Expand Down Expand Up @@ -646,7 +657,7 @@ func b2s(b []byte) string { return *(*string)(unsafe.Pointer(&b)) }

func getObjectType(object any) ObjectType {
switch object.(type) {
case string, []byte:
case []byte:
return TypeString
case int:
return TypeInteger
Expand Down
21 changes: 13 additions & 8 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ const (
)

func TestCommand(t *testing.T) {
go func() {

}()

t.Run(testTypeMiniRedis, func(t *testing.T) {
s := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{
Expand Down Expand Up @@ -287,6 +283,10 @@ func testCommand(t *testing.T, testType string, rdb *redis.Client, sleepFn func(
n, _ := rdb.SAdd(ctx, "set", "k1", "k2", "k3").Result()
ast.Equal(n, int64(3))

// smembers
mems, _ := rdb.SMembers(ctx, "set").Result()
ast.ElementsMatch(mems, []string{"k1", "k2", "k3"})

// spop
for i := 0; i < 3; i++ {
val, _ := rdb.SPop(ctx, "set").Result()
Expand Down Expand Up @@ -490,20 +490,25 @@ func testCommand(t *testing.T, testType string, rdb *redis.Client, sleepFn func(

t.Run("save", func(t *testing.T) {
rdb.FlushDB(ctx)

// set key
rdb.Set(ctx, "rdb-key1", 123, 0)
rdb.Set(ctx, "rdb-key2", 123, time.Minute)
rdb.Incr(ctx, "key-incr")
rdb.HSet(ctx, "rdb-hash1", "k1", "v1", "k2", "v2")
rdb.SAdd(ctx, "rdb-set1", "k1", "k2")
//rdb.LPush(ctx, "rdb-list1", "k1", "k2")
for i := 0; i < 1024; i++ {
key := fmt.Sprintf("%d", i)
rdb.HSet(ctx, "rdb-hash2", key, key)
rdb.SAdd(ctx, "rdb-set2", key)
}
rdb.RPush(ctx, "rdb-list1", "k1", "k2", "k3")

res, err := rdb.Save(context.Background()).Result()
ast.Nil(err)
ast.Equal(res, "OK")

//err = db.rdb.LoadDB()
//ast.Nil(err)
_, err = rdb.Do(ctx, "load").Result()
ast.Nil(err)
})
}

Expand Down
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"port": 6379,
"appendonly": false,
"appendfilename": "appendonly.aof",
"save": false,
"save": true,
"savefilename": "dump.rdb"
}
16 changes: 16 additions & 0 deletions internal/list/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package list

import (
"github.com/xgzlucario/rotom/internal/resp"
"math/rand/v2"
"testing"

Expand Down Expand Up @@ -98,6 +99,21 @@ func FuzzTestList(f *testing.F) {
ast.ElementsMatch(keys1, keys3)

case 7: // Marshal
if len(slice) == 0 {
return
}
writer := resp.NewWriter(0)

// listpack
ast.Nil(lp.Encode(writer))
lp = NewListPack()
ast.Nil(lp.Decode(resp.NewReader(writer.Bytes())))
writer.Reset()

// list
ast.Nil(ls.Encode(writer))
ls = New()
ast.Nil(ls.Decode(resp.NewReader(writer.Bytes())))
}
})
}
36 changes: 32 additions & 4 deletions internal/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@ import (
// based on listpack rather than ziplist to optimize cascade update.
type QuickList struct {
size int
ls *list.List[*ListPack] // linked-list
ls *list.List[*ListPack]
}

// New create a quicklist instance.
func New() *QuickList {
ls := list.New[*ListPack]()
ls.PushFront(NewListPack())
return &QuickList{ls: ls}
return &QuickList{
ls: list.New[*ListPack](),
}
}

func (ls *QuickList) head() *ListPack {
if ls.ls.Front == nil {
ls.ls.PushFront(NewListPack())
}
return ls.ls.Front.Value
}

func (ls *QuickList) tail() *ListPack {
if ls.ls.Back == nil {
ls.ls.PushBack(NewListPack())
}
return ls.ls.Back.Value
}

Expand Down Expand Up @@ -120,9 +126,31 @@ func (ls *QuickList) Range(start int, fn func(key []byte) (stop bool)) {
}

func (ls *QuickList) Encode(writer *resp.Writer) error {
num := 0
for n := ls.ls.Front; n != nil; n = n.Next {
num++
}
writer.WriteArrayHead(num)
for n := ls.ls.Front; n != nil; n = n.Next {
if err := n.Value.Encode(writer); err != nil {
return err
}
}
return nil
}

func (ls *QuickList) Decode(reader *resp.Reader) error {
n, err := reader.ReadArrayHead()
if err != nil {
return err
}
for range n {
lp := NewListPack()
if err = lp.Decode(reader); err != nil {
return err
}
ls.ls.PushBack(lp)
ls.size += lp.Size()
}
return nil
}
7 changes: 4 additions & 3 deletions internal/list/listpack.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package list

import (
"bytes"
"encoding/binary"
"github.com/xgzlucario/rotom/internal/pool"
"github.com/xgzlucario/rotom/internal/resp"
Expand Down Expand Up @@ -81,13 +82,13 @@ func (lp *ListPack) Iterator() *LpIterator {
}

func (lp *ListPack) Encode(writer *resp.Writer) error {
writer.WriteArrayHead(int(lp.size))
writer.WriteInteger(int(lp.size))
writer.WriteBulk(lp.data)
return nil
}

func (lp *ListPack) Decode(reader *resp.Reader) error {
n, err := reader.ReadArrayHead()
n, err := reader.ReadInteger()
if err != nil {
return err
}
Expand All @@ -96,7 +97,7 @@ func (lp *ListPack) Decode(reader *resp.Reader) error {
return err
}
lp.size = uint32(n)
lp.data = data
lp.data = bytes.Clone(data)
return nil
}

Expand Down
Loading

0 comments on commit 23baf4e

Please sign in to comment.