diff --git a/src/datastruct/dict/dict.go b/src/datastruct/dict/dict.go index d1fa8ce..881ee61 100644 --- a/src/datastruct/dict/dict.go +++ b/src/datastruct/dict/dict.go @@ -14,6 +14,11 @@ type Dict struct { rehashIndex int32 } +type Shard struct { + head *Node + mutex sync.RWMutex +} + type Node struct { key string val interface{} @@ -21,10 +26,6 @@ type Node struct { hashCode uint32 } -type Shard struct { - head *Node - mutex sync.RWMutex -} const ( maxCapacity = 1 << 15 diff --git a/src/datastruct/list/linked.go b/src/datastruct/list/linked.go index 1b50275..131f309 100644 --- a/src/datastruct/list/linked.go +++ b/src/datastruct/list/linked.go @@ -1,5 +1,7 @@ package list +import "github.com/HDT3213/godis/src/datastruct/utils" + type LinkedList struct { first *node last *node @@ -31,7 +33,7 @@ func (list *LinkedList)Add(val interface{}) { list.size++ } -func (list *LinkedList)find(index int)(val *node) { +func (list *LinkedList)find(index int)(n *node) { if index < list.size / 2 { n := list.first for i := 0; i < index; i++ { @@ -57,16 +59,28 @@ func (list *LinkedList)Get(index int)(val interface{}) { return list.find(index).val } +func (list *LinkedList)Set(index int, val interface{}) { + if list == nil { + panic("list is nil") + } + if index < 0 || index > list.size { + panic("index out of bound") + } + n := list.find(index) + n.val = val +} + func (list *LinkedList)Insert(index int, val interface{}) { if list == nil { panic("list is nil") } - if index < 0 || index >= list.size { + if index < 0 || index > list.size { panic("index out of bound") } if index == list.size { list.Add(val) + return } else { // list is not empty pivot := list.find(index) @@ -81,19 +95,11 @@ func (list *LinkedList)Insert(index int, val interface{}) { pivot.prev.next = n } pivot.prev = n + list.size++ } - list.size++ } -func (list *LinkedList)Remove(index int) { - if list == nil { - panic("list is nil") - } - if index < 0 || index >= list.size { - panic("index out of bound") - } - - n := list.find(index) +func (list *LinkedList)removeNode(n *node) { if n.prev == nil { list.first = n.next } else { @@ -108,11 +114,132 @@ func (list *LinkedList)Remove(index int) { // for gc n.prev = nil n.next = nil - n.val = nil list.size-- } +func (list *LinkedList)Remove(index int)(val interface{}) { + if list == nil { + panic("list is nil") + } + if index < 0 || index >= list.size { + panic("index out of bound") + } + + n := list.find(index) + list.removeNode(n) + return n.val +} + +func (list *LinkedList)RemoveLast()(val interface{}) { + if list == nil { + panic("list is nil") + } + if list.last == nil { + // empty list + return nil + } + n := list.last + list.removeNode(n) + return n.val +} + +func (list *LinkedList)RemoveAllByVal(val interface{})int { + if list == nil { + panic("list is nil") + } + n := list.first + removed := 0 + for n != nil { + var toRemoveNode *node + if utils.Equals(n.val, val) { + toRemoveNode = n + } + if n.next == nil { + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + break + } else { + n = n.next + } + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + } + return removed +} + +/** + * remove at most `count` values of the specified value in this list + * scan from left to right + */ +func (list *LinkedList) RemoveByVal(val interface{}, count int)int { + if list == nil { + panic("list is nil") + } + n := list.first + removed := 0 + for n != nil { + var toRemoveNode *node + if utils.Equals(n.val, val) { + toRemoveNode = n + } + if n.next == nil { + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + break + } else { + n = n.next + } + + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + if removed == count { + break + } + } + return removed +} + +func (list *LinkedList) ReverseRemoveByVal(val interface{}, count int)int { + if list == nil { + panic("list is nil") + } + n := list.last + removed := 0 + for n != nil { + var toRemoveNode *node + if utils.Equals(n.val, val) { + toRemoveNode = n + } + if n.prev == nil { + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + break + } else { + n = n.prev + } + + if toRemoveNode != nil { + removed++ + list.removeNode(toRemoveNode) + } + if removed == count { + break + } + } + return removed +} + func (list *LinkedList)Len()int { if list == nil { panic("list is nil") @@ -137,7 +264,48 @@ func (list *LinkedList)ForEach(consumer func(int, interface{})bool) { } } -func Make(vals ...interface{})(*LinkedList) { +func (list *LinkedList)Range(start int, stop int)[]interface{} { + if list == nil { + panic("list is nil") + } + if start < 0 || start >= list.size { + panic("`start` out of range") + } + if stop < start || stop > list.size { + panic("`stop` out of range") + } + + sliceSize := stop - start + slice := make([]interface{}, sliceSize) + n := list.first + i := 0 + sliceIndex := 0 + for n != nil { + if i >= start && i < stop { + slice[sliceIndex] = n.val + sliceIndex++ + } else if i >= stop { + break + } + if n.next == nil { + break + } else { + i++ + n = n.next + } + } + return slice +} + +func Make(vals ...interface{}) *LinkedList { + list := LinkedList{} + for _, v := range vals { + list.Add(v) + } + return &list +} + +func MakeBytesList(vals ...[]byte) *LinkedList { list := LinkedList{} for _, v := range vals { list.Add(v) diff --git a/src/datastruct/list/linked_test.go b/src/datastruct/list/linked_test.go index 1871703..f2e82af 100644 --- a/src/datastruct/list/linked_test.go +++ b/src/datastruct/list/linked_test.go @@ -64,6 +64,69 @@ func TestRemove(t *testing.T) { } } +func TestRemoveVal(t *testing.T) { + list := Make() + for i := 0; i < 10; i++ { + list.Add(i) + list.Add(i) + } + for index := 0; index < list.Len(); index++ { + list.RemoveAllByVal(index) + list.ForEach(func(i int, v interface{}) bool { + intVal, _ := v.(int) + if intVal == index { + t.Error("remove test fail: found " + strconv.Itoa(index) + " at index: " + strconv.Itoa(i)) + } + return true + }) + } + + list = Make() + for i := 0; i < 10; i++ { + list.Add(i) + list.Add(i) + } + for i := 0; i < 10; i++ { + list.RemoveByVal(i, 1) + } + list.ForEach(func(i int, v interface{}) bool { + intVal, _ := v.(int) + if intVal != i { + t.Error("test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal)) + } + return true + }) + for i := 0; i < 10; i++ { + list.RemoveByVal(i, 1) + } + if list.Len() != 0 { + t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len())) + } + + list = Make() + for i := 0; i < 10; i++ { + list.Add(i) + list.Add(i) + } + for i := 0; i < 10; i++ { + list.ReverseRemoveByVal(i, 1) + } + list.ForEach(func(i int, v interface{}) bool { + intVal, _ := v.(int) + if intVal != i { + t.Error("test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal)) + } + return true + }) + for i := 0; i < 10; i++ { + list.ReverseRemoveByVal(i, 1) + } + if list.Len() != 0 { + t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len())) + } + +} + func TestInsert(t *testing.T) { list := Make() for i := 0; i < 10; i++ { @@ -109,3 +172,44 @@ func TestInsert(t *testing.T) { } } + +func TestRemoveLast(t *testing.T) { + list := Make() + for i := 0; i < 10; i++ { + list.Add(i) + } + for i := 9; i >= 0; i-- { + val := list.RemoveLast() + intVal, _ := val.(int) + if intVal != i { + t.Error("add test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal)) + } + } +} + +func TestRange(t *testing.T) { + list := Make() + size := 10 + for i := 0; i < size; i++ { + list.Add(i) + } + for start := 0; start < size; start++ { + for stop := start; stop < size; stop++ { + slice := list.Range(start, stop) + if len(slice) != stop - start { + t.Error("expected " + strconv.Itoa(stop - start) + ", get: " + strconv.Itoa(len(slice)) + + ", range: [" + strconv.Itoa(start) + "," + strconv.Itoa(stop) + "]") + } + sliceIndex := 0 + for i := start; i < stop; i++ { + val := slice[sliceIndex] + intVal, _ := val.(int) + if intVal != i { + t.Error("expected " + strconv.Itoa(i) + ", get: " + strconv.Itoa(intVal) + + ", range: [" + strconv.Itoa(start) + "," + strconv.Itoa(stop) + "]") + } + sliceIndex++ + } + } + } +} \ No newline at end of file diff --git a/src/datastruct/utils/utils.go b/src/datastruct/utils/utils.go new file mode 100644 index 0000000..ee18895 --- /dev/null +++ b/src/datastruct/utils/utils.go @@ -0,0 +1,28 @@ +package utils + +func Equals(a interface{}, b interface{})bool { + sliceA, okA := a.([]byte) + sliceB, okB := b.([]byte) + if okA && okB { + return sliceEquals(sliceA, sliceB) + } + return a == b +} + +func sliceEquals(a []byte, b []byte)bool { + if (a == nil && b != nil) || (a != nil && b == nil) { + return false + } + if len(a) != len(b) { + return false + } + size := len(a) + for i := 0; i < size; i++ { + av := a[i] + bv := b[i] + if av != bv { + return false + } + } + return true +} diff --git a/src/db/db.go b/src/db/db.go index b96d111..7275850 100644 --- a/src/db/db.go +++ b/src/db/db.go @@ -1,13 +1,14 @@ package db import ( - "strings" - "github.com/HDT3213/godis/src/redis/reply" "fmt" - "runtime/debug" - "github.com/HDT3213/godis/src/lib/logger" - "github.com/HDT3213/godis/src/interface/redis" "github.com/HDT3213/godis/src/datastruct/dict" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/lib/logger" + "github.com/HDT3213/godis/src/redis/reply" + "runtime/debug" + "strings" + "sync" ) const ( @@ -22,6 +23,7 @@ type DataEntity struct { Code uint8 TTL int64 // ttl in seconds, 0 for unlimited ttl Data interface{} + sync.RWMutex } // args don't include cmd line @@ -44,6 +46,19 @@ func MakeCmdMap()map[string]CmdFunc { cmdMap["get"] = Get + cmdMap["lpush"] = LPush + cmdMap["lpushx"] = LPushX + cmdMap["rpush"] = RPush + cmdMap["rpushx"] = RPushX + cmdMap["lpop"] = LPop + cmdMap["rpop"] = RPop + cmdMap["rpoplpush"] = RPopLPush + cmdMap["lrem"] = LRem + cmdMap["llen"] = LLen + cmdMap["lindex"] = LIndex + cmdMap["lset"] = LSet + cmdMap["lrange"] = LRange + return cmdMap } diff --git a/src/db/lindex.go b/src/db/lindex.go new file mode 100644 index 0000000..bfd431f --- /dev/null +++ b/src/db/lindex.go @@ -0,0 +1,49 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" + "strconv" +) + +func LIndex(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lindex' command") + } + key := string(args[0]) + index64, err := strconv.ParseInt(string(args[1]), 10, 64) + if err != nil { + return reply.MakeErrReply("ERR value is not an integer or out of range") + } + index := int(index64) + + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return &reply.NullBulkReply{} + } else { + entity, _ = rawEntity.(*DataEntity) + } + entity.RLock() + defer entity.RUnlock() + + // check type + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + + list, _ := entity.Data.(*List.LinkedList) + size := list.Len() // assert: size > 0 + if index < -1 * size { + return &reply.NullBulkReply{} + } else if index < 0 { + index = size + index + } else if index >= size { + return &reply.NullBulkReply{} + } + + val, _ := list.Get(index).([]byte) + return reply.MakeBulkReply(val) +} diff --git a/src/db/llen.go b/src/db/llen.go new file mode 100644 index 0000000..3809286 --- /dev/null +++ b/src/db/llen.go @@ -0,0 +1,34 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func LLen(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 1 { + return reply.MakeErrReply("ERR wrong number of arguments for 'llen' command") + } + key := string(args[0]) + + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return reply.MakeIntReply(0) + } else { + entity, _ = rawEntity.(*DataEntity) + } + entity.RLock() + defer entity.RUnlock() + + // check type + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + + list, _ := entity.Data.(*List.LinkedList) + size := int64(list.Len()) + return reply.MakeIntReply(size) +} \ No newline at end of file diff --git a/src/db/lpop.go b/src/db/lpop.go new file mode 100644 index 0000000..bf6d937 --- /dev/null +++ b/src/db/lpop.go @@ -0,0 +1,38 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func LPop(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 1 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lindex' command") + } + key := string(args[0]) + + // get data + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return &reply.NullBulkReply{} + } else { + entity, _ = rawEntity.(*DataEntity) + } + entity.Lock() + defer entity.Unlock() + + // check type + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + + list, _ := entity.Data.(*List.LinkedList) + val, _ := list.Remove(0).([]byte) + if list.Len() == 0 { + db.Data.Remove(key) + } + return reply.MakeBulkReply(val) +} diff --git a/src/db/lpush.go b/src/db/lpush.go new file mode 100644 index 0000000..8fd7ee2 --- /dev/null +++ b/src/db/lpush.go @@ -0,0 +1,73 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func LPush(db *DB, args [][]byte)redis.Reply { + if len(args) < 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lpush' command") + } + key := string(args[0]) + values := args[1:] + + // get or init entity + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + entity = &DataEntity{ + Code: ListCode, + Data: &List.LinkedList{}, + } + } else { + entity, _ = rawEntity.(*DataEntity) + } + entity.Lock() + defer entity.Unlock() + + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + + // insert + list, _ := entity.Data.(*List.LinkedList) + for _, value := range values { + list.Insert(0, value) + } + db.Data.Put(key, entity) + + return reply.MakeIntReply(int64(list.Len())) +} + +func LPushX(db *DB, args [][]byte)redis.Reply { + if len(args) < 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lpush' command") + } + key := string(args[0]) + values := args[1:] + + // get or init entity + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return reply.MakeIntReply(0) + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + // insert + list, _ := entity.Data.(*List.LinkedList) + for _, value := range values { + list.Insert(0, value) + } + db.Data.Put(key, entity) + + return reply.MakeIntReply(int64(list.Len())) +} \ No newline at end of file diff --git a/src/db/lrange.go b/src/db/lrange.go new file mode 100644 index 0000000..56e01b1 --- /dev/null +++ b/src/db/lrange.go @@ -0,0 +1,72 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" + "strconv" +) + +func LRange(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 3 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lrange' command") + } + key := string(args[0]) + start64, err := strconv.ParseInt(string(args[1]), 10, 64) + if err != nil { + return reply.MakeErrReply("ERR value is not an integer or out of range") + } + start := int(start64) + stop64, err := strconv.ParseInt(string(args[2]), 10, 64) + if err != nil { + return reply.MakeErrReply("ERR value is not an integer or out of range") + } + stop := int(stop64) + + // get data + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return &reply.EmptyMultiBulkReply{} + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.RLock() + defer entity.RUnlock() + + // compute index + list, _ := entity.Data.(*List.LinkedList) + size := list.Len() // assert: size > 0 + if start < -1 * size { + start = 0 + } else if start < 0 { + start = size + start + } else if start >= size { + return &reply.EmptyMultiBulkReply{} + } + if stop < -1 * size { + stop = 0 + } else if stop < 0 { + stop = size + stop + 1 + } else if stop < size { + stop = stop + 1 + } else { + stop = size + } + if stop < start { + stop = start + } + + // assert: start in [0, size - 1], stop in [start, size] + slice := list.Range(start, stop) + result := make([][]byte, len(slice)) + for i, raw := range slice { + bytes, _ := raw.([]byte) + result[i] = bytes + } + return reply.MakeMultiBulkReply(result) +} diff --git a/src/db/lrem.go b/src/db/lrem.go new file mode 100644 index 0000000..42cfb54 --- /dev/null +++ b/src/db/lrem.go @@ -0,0 +1,52 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" + "strconv" +) + +func LRem(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 3 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lrem' command") + } + key := string(args[0]) + count64, err := strconv.ParseInt(string(args[1]), 10, 64) + if err != nil { + return reply.MakeErrReply("ERR value is not an integer or out of range") + } + count := int(count64) + value := args[2] + + // get data entity + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return reply.MakeIntReply(0) + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + list, _ := entity.Data.(*List.LinkedList) + var removed int + if count == 0 { + removed = list.RemoveAllByVal(value) + } else if count > 0 { + removed = list.RemoveByVal(value, count) + } else { + removed = list.ReverseRemoveByVal(value, -count) + } + + if list.Len() == 0 { + db.Data.Remove(key) + } + + return reply.MakeIntReply(int64(removed)) +} \ No newline at end of file diff --git a/src/db/lset.go b/src/db/lset.go new file mode 100644 index 0000000..14623f8 --- /dev/null +++ b/src/db/lset.go @@ -0,0 +1,49 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" + "strconv" +) + +func LSet(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 3 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lset' command") + } + key := string(args[0]) + index64, err := strconv.ParseInt(string(args[1]), 10, 64) + if err != nil { + return reply.MakeErrReply("ERR value is not an integer or out of range") + } + index := int(index64) + value := args[2] + + // get data + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return reply.MakeErrReply("ERR no such key") + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + list, _ := entity.Data.(*List.LinkedList) + size := list.Len() // assert: size > 0 + if index < -1 * size { + return reply.MakeErrReply("ERR index out of range") + } else if index < 0 { + index = size + index + } else if index >= size { + return reply.MakeErrReply("ERR index out of range") + } + + list.Set(index, value) + return &reply.OkReply{} +} \ No newline at end of file diff --git a/src/db/rpop.go b/src/db/rpop.go new file mode 100644 index 0000000..c3355e5 --- /dev/null +++ b/src/db/rpop.go @@ -0,0 +1,36 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func RPop(db *DB, args [][]byte)redis.Reply { + // parse args + if len(args) != 1 { + return reply.MakeErrReply("ERR wrong number of arguments for 'lindex' command") + } + key := string(args[0]) + + // get data + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return &reply.NullBulkReply{} + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + list, _ := entity.Data.(*List.LinkedList) + val, _ := list.RemoveLast().([]byte) + if list.Len() == 0 { + db.Data.Remove(key) + } + return reply.MakeBulkReply(val) +} diff --git a/src/db/rpoplpush.go b/src/db/rpoplpush.go new file mode 100644 index 0000000..a71473a --- /dev/null +++ b/src/db/rpoplpush.go @@ -0,0 +1,49 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func RPopLPush(db *DB, args [][]byte)redis.Reply { + if len(args) != 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'rpoplpush' command") + } + sourceKey := string(args[0]) + destKey := string(args[1]) + + // get source entity + rawEntity, exists := db.Data.Get(sourceKey) + var sourceEntity *DataEntity + if !exists { + return &reply.NullBulkReply{} + } else { + sourceEntity, _ = rawEntity.(*DataEntity) + } + sourceList, _ := sourceEntity.Data.(*List.LinkedList) + sourceEntity.Lock() + defer sourceEntity.Unlock() + + // get dest entity + rawEntity, exists = db.Data.Get(destKey) + var destEntity *DataEntity + if !exists { + destEntity = &DataEntity{ + Code: ListCode, + Data: &List.LinkedList{}, + } + db.Data.Put(destKey, destEntity) + } else { + destEntity, _ = rawEntity.(*DataEntity) + } + destList, _ := destEntity.Data.(*List.LinkedList) + destEntity.Lock() + defer destEntity.Unlock() + + // pop and push + val, _ := sourceList.RemoveLast().([]byte) + destList.Insert(0, val) + + return reply.MakeBulkReply(val) +} diff --git a/src/db/rpush.go b/src/db/rpush.go new file mode 100644 index 0000000..5283a50 --- /dev/null +++ b/src/db/rpush.go @@ -0,0 +1,72 @@ +package db + +import ( + List "github.com/HDT3213/godis/src/datastruct/list" + "github.com/HDT3213/godis/src/interface/redis" + "github.com/HDT3213/godis/src/redis/reply" +) + +func RPush(db *DB, args [][]byte)redis.Reply { + if len(args) < 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'rpush' command") + } + key := string(args[0]) + values := args[1:] + + // get or init entity + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + entity = &DataEntity{ + Code: ListCode, + Data: &List.LinkedList{}, + } + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + // put list + list, _ := entity.Data.(*List.LinkedList) + for _, value := range values { + list.Add(value) + } + db.Data.Put(key, entity) + + return reply.MakeIntReply(int64(list.Len())) +} + +func RPushX(db *DB, args [][]byte)redis.Reply { + if len(args) < 2 { + return reply.MakeErrReply("ERR wrong number of arguments for 'rpush' command") + } + key := string(args[0]) + values := args[1:] + + // get or init entity + rawEntity, exists := db.Data.Get(key) + var entity *DataEntity + if !exists { + return reply.MakeIntReply(0) + } else { + entity, _ = rawEntity.(*DataEntity) + } + if entity.Code != ListCode { + return &reply.WrongTypeErrReply{} + } + entity.Lock() + defer entity.Unlock() + + // put list + list, _ := entity.Data.(*List.LinkedList) + for _, value := range values { + list.Add(value) + } + db.Data.Put(key, entity) + + return reply.MakeIntReply(int64(list.Len())) +} \ No newline at end of file diff --git a/src/redis/reply/consts.go b/src/redis/reply/consts.go index 853fa57..aea94cf 100644 --- a/src/redis/reply/consts.go +++ b/src/redis/reply/consts.go @@ -10,10 +10,10 @@ func (r *PongReply)ToBytes()[]byte { type OkReply struct {} -var OkBytes = []byte("+OK\r\n") +var okBytes = []byte("+OK\r\n") func (r *OkReply)ToBytes()[]byte { - return OkBytes + return okBytes } var nullBulkBytes = []byte("$-1\r\n") @@ -22,4 +22,12 @@ type NullBulkReply struct {} func (r *NullBulkReply)ToBytes()[]byte { return nullBulkBytes +} + +var emptyMultiBulkBytes = []byte("*0\r\n") + +type EmptyMultiBulkReply struct {} + +func (r *EmptyMultiBulkReply)ToBytes()[]byte { + return emptyMultiBulkBytes } \ No newline at end of file