mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-06 17:26:52 +08:00
basic list
This commit is contained in:
@@ -14,6 +14,11 @@ type Dict struct {
|
|||||||
rehashIndex int32
|
rehashIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Shard struct {
|
||||||
|
head *Node
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
key string
|
key string
|
||||||
val interface{}
|
val interface{}
|
||||||
@@ -21,10 +26,6 @@ type Node struct {
|
|||||||
hashCode uint32
|
hashCode uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shard struct {
|
|
||||||
head *Node
|
|
||||||
mutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxCapacity = 1 << 15
|
maxCapacity = 1 << 15
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
|
import "github.com/HDT3213/godis/src/datastruct/utils"
|
||||||
|
|
||||||
type LinkedList struct {
|
type LinkedList struct {
|
||||||
first *node
|
first *node
|
||||||
last *node
|
last *node
|
||||||
@@ -31,7 +33,7 @@ func (list *LinkedList)Add(val interface{}) {
|
|||||||
list.size++
|
list.size++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *LinkedList)find(index int)(val *node) {
|
func (list *LinkedList)find(index int)(n *node) {
|
||||||
if index < list.size / 2 {
|
if index < list.size / 2 {
|
||||||
n := list.first
|
n := list.first
|
||||||
for i := 0; i < index; i++ {
|
for i := 0; i < index; i++ {
|
||||||
@@ -57,16 +59,28 @@ func (list *LinkedList)Get(index int)(val interface{}) {
|
|||||||
return list.find(index).val
|
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{}) {
|
func (list *LinkedList)Insert(index int, val interface{}) {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is nil")
|
panic("list is nil")
|
||||||
}
|
}
|
||||||
if index < 0 || index >= list.size {
|
if index < 0 || index > list.size {
|
||||||
panic("index out of bound")
|
panic("index out of bound")
|
||||||
}
|
}
|
||||||
|
|
||||||
if index == list.size {
|
if index == list.size {
|
||||||
list.Add(val)
|
list.Add(val)
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
// list is not empty
|
// list is not empty
|
||||||
pivot := list.find(index)
|
pivot := list.find(index)
|
||||||
@@ -81,19 +95,11 @@ func (list *LinkedList)Insert(index int, val interface{}) {
|
|||||||
pivot.prev.next = n
|
pivot.prev.next = n
|
||||||
}
|
}
|
||||||
pivot.prev = n
|
pivot.prev = n
|
||||||
}
|
|
||||||
list.size++
|
list.size++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *LinkedList)Remove(index int) {
|
func (list *LinkedList)removeNode(n *node) {
|
||||||
if list == nil {
|
|
||||||
panic("list is nil")
|
|
||||||
}
|
|
||||||
if index < 0 || index >= list.size {
|
|
||||||
panic("index out of bound")
|
|
||||||
}
|
|
||||||
|
|
||||||
n := list.find(index)
|
|
||||||
if n.prev == nil {
|
if n.prev == nil {
|
||||||
list.first = n.next
|
list.first = n.next
|
||||||
} else {
|
} else {
|
||||||
@@ -108,11 +114,132 @@ func (list *LinkedList)Remove(index int) {
|
|||||||
// for gc
|
// for gc
|
||||||
n.prev = nil
|
n.prev = nil
|
||||||
n.next = nil
|
n.next = nil
|
||||||
n.val = nil
|
|
||||||
|
|
||||||
list.size--
|
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 {
|
func (list *LinkedList)Len()int {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is 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{}
|
list := LinkedList{}
|
||||||
for _, v := range vals {
|
for _, v := range vals {
|
||||||
list.Add(v)
|
list.Add(v)
|
||||||
|
@@ -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) {
|
func TestInsert(t *testing.T) {
|
||||||
list := Make()
|
list := Make()
|
||||||
for i := 0; i < 10; i++ {
|
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++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/datastruct/utils/utils.go
Normal file
28
src/datastruct/utils/utils.go
Normal file
@@ -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
|
||||||
|
}
|
25
src/db/db.go
25
src/db/db.go
@@ -1,13 +1,14 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
|
||||||
"fmt"
|
"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/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 (
|
const (
|
||||||
@@ -22,6 +23,7 @@ type DataEntity struct {
|
|||||||
Code uint8
|
Code uint8
|
||||||
TTL int64 // ttl in seconds, 0 for unlimited ttl
|
TTL int64 // ttl in seconds, 0 for unlimited ttl
|
||||||
Data interface{}
|
Data interface{}
|
||||||
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// args don't include cmd line
|
// args don't include cmd line
|
||||||
@@ -44,6 +46,19 @@ func MakeCmdMap()map[string]CmdFunc {
|
|||||||
|
|
||||||
cmdMap["get"] = Get
|
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
|
return cmdMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
src/db/lindex.go
Normal file
49
src/db/lindex.go
Normal file
@@ -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)
|
||||||
|
}
|
34
src/db/llen.go
Normal file
34
src/db/llen.go
Normal file
@@ -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)
|
||||||
|
}
|
38
src/db/lpop.go
Normal file
38
src/db/lpop.go
Normal file
@@ -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)
|
||||||
|
}
|
73
src/db/lpush.go
Normal file
73
src/db/lpush.go
Normal file
@@ -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()))
|
||||||
|
}
|
72
src/db/lrange.go
Normal file
72
src/db/lrange.go
Normal file
@@ -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)
|
||||||
|
}
|
52
src/db/lrem.go
Normal file
52
src/db/lrem.go
Normal file
@@ -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))
|
||||||
|
}
|
49
src/db/lset.go
Normal file
49
src/db/lset.go
Normal file
@@ -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{}
|
||||||
|
}
|
36
src/db/rpop.go
Normal file
36
src/db/rpop.go
Normal file
@@ -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)
|
||||||
|
}
|
49
src/db/rpoplpush.go
Normal file
49
src/db/rpoplpush.go
Normal file
@@ -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)
|
||||||
|
}
|
72
src/db/rpush.go
Normal file
72
src/db/rpush.go
Normal file
@@ -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()))
|
||||||
|
}
|
@@ -10,10 +10,10 @@ func (r *PongReply)ToBytes()[]byte {
|
|||||||
|
|
||||||
type OkReply struct {}
|
type OkReply struct {}
|
||||||
|
|
||||||
var OkBytes = []byte("+OK\r\n")
|
var okBytes = []byte("+OK\r\n")
|
||||||
|
|
||||||
func (r *OkReply)ToBytes()[]byte {
|
func (r *OkReply)ToBytes()[]byte {
|
||||||
return OkBytes
|
return okBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
var nullBulkBytes = []byte("$-1\r\n")
|
var nullBulkBytes = []byte("$-1\r\n")
|
||||||
@@ -23,3 +23,11 @@ type NullBulkReply struct {}
|
|||||||
func (r *NullBulkReply)ToBytes()[]byte {
|
func (r *NullBulkReply)ToBytes()[]byte {
|
||||||
return nullBulkBytes
|
return nullBulkBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyMultiBulkBytes = []byte("*0\r\n")
|
||||||
|
|
||||||
|
type EmptyMultiBulkReply struct {}
|
||||||
|
|
||||||
|
func (r *EmptyMultiBulkReply)ToBytes()[]byte {
|
||||||
|
return emptyMultiBulkBytes
|
||||||
|
}
|
Reference in New Issue
Block a user