mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-14 21:05:43 +08:00
refactor: lock key instead of entity
This commit is contained in:
131
src/datastruct/lock/lock_map.go
Normal file
131
src/datastruct/lock/lock_map.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package lock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LockMap struct {
|
||||||
|
m sync.Map // key -> mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)Lock(key string) {
|
||||||
|
mu := &sync.RWMutex{}
|
||||||
|
existed, loaded := lock.m.LoadOrStore(key, mu)
|
||||||
|
if loaded {
|
||||||
|
mu, _ = existed.(*sync.RWMutex)
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)RLock(key string) {
|
||||||
|
mu := &sync.RWMutex{}
|
||||||
|
existed, loaded := lock.m.LoadOrStore(key, mu)
|
||||||
|
if loaded {
|
||||||
|
mu, _ = existed.(*sync.RWMutex)
|
||||||
|
}
|
||||||
|
mu.RLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)UnLock(key string) {
|
||||||
|
value, ok := lock.m.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mu := value.(*sync.RWMutex)
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)RUnLock(key string) {
|
||||||
|
value, ok := lock.m.Load(key)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mu := value.(*sync.RWMutex)
|
||||||
|
mu.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)Locks(keys ...string) {
|
||||||
|
keySlice := make(sort.StringSlice, len(keys))
|
||||||
|
copy(keySlice, keys)
|
||||||
|
sort.Sort(keySlice)
|
||||||
|
for _, key := range keySlice {
|
||||||
|
lock.Lock(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)RLocks(keys ...string) {
|
||||||
|
keySlice := make(sort.StringSlice, len(keys))
|
||||||
|
copy(keySlice, keys)
|
||||||
|
sort.Sort(keySlice)
|
||||||
|
for _, key := range keySlice {
|
||||||
|
lock.RLock(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (lock *LockMap)UnLocks(keys ...string) {
|
||||||
|
size := len(keys)
|
||||||
|
keySlice := make(sort.StringSlice, size)
|
||||||
|
copy(keySlice, keys)
|
||||||
|
sort.Sort(keySlice)
|
||||||
|
for i := size - 1; i >= 0; i-- {
|
||||||
|
key := keySlice[i]
|
||||||
|
lock.UnLock(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)RUnLocks(keys ...string) {
|
||||||
|
size := len(keys)
|
||||||
|
keySlice := make(sort.StringSlice, size)
|
||||||
|
copy(keySlice, keys)
|
||||||
|
sort.Sort(keySlice)
|
||||||
|
for i := size - 1; i >= 0; i-- {
|
||||||
|
key := keySlice[i]
|
||||||
|
lock.RUnLock(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)Clean(key string) {
|
||||||
|
lock.m.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock *LockMap)Cleans(keys ...string) {
|
||||||
|
for _, key := range keys {
|
||||||
|
lock.Clean(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GoID() int {
|
||||||
|
var buf [64]byte
|
||||||
|
n := runtime.Stack(buf[:], false)
|
||||||
|
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
|
||||||
|
id, err := strconv.Atoi(idField)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func debug() {
|
||||||
|
lm := LockMap{}
|
||||||
|
size := 10
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
lm.Locks("1", "2")
|
||||||
|
println("go: " + strconv.Itoa(GoID()))
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
println("go: " + strconv.Itoa(GoID()))
|
||||||
|
lm.UnLocks("1", "2")
|
||||||
|
wg.Done()
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
37
src/db/db.go
37
src/db/db.go
@@ -3,12 +3,12 @@ package db
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/HDT3213/godis/src/datastruct/dict"
|
"github.com/HDT3213/godis/src/datastruct/dict"
|
||||||
|
"github.com/HDT3213/godis/src/datastruct/lock"
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
"github.com/HDT3213/godis/src/interface/redis"
|
||||||
"github.com/HDT3213/godis/src/lib/logger"
|
"github.com/HDT3213/godis/src/lib/logger"
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
"github.com/HDT3213/godis/src/redis/reply"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -23,10 +23,6 @@ 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{}
|
||||||
|
|
||||||
// dict will ensure thread safety (by using mutex) of its method
|
|
||||||
// use this mutex for complicated command only, eg. rpush, incr ...
|
|
||||||
sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataEntityWithKey struct {
|
type DataEntityWithKey struct {
|
||||||
@@ -38,7 +34,12 @@ type DataEntityWithKey struct {
|
|||||||
type CmdFunc func(db *DB, args [][]byte)redis.Reply
|
type CmdFunc func(db *DB, args [][]byte)redis.Reply
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
Data *dict.Dict // key -> DataEntity
|
// key -> DataEntity
|
||||||
|
Data *dict.Dict
|
||||||
|
|
||||||
|
// dict will ensure thread safety of its method
|
||||||
|
// use this mutex for complicated command only, eg. rpush, incr ...
|
||||||
|
Locks *lock.LockMap
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdMap = MakeCmdMap()
|
var cmdMap = MakeCmdMap()
|
||||||
@@ -53,6 +54,7 @@ func MakeCmdMap()map[string]CmdFunc {
|
|||||||
cmdMap["psetex"] = PSetEX
|
cmdMap["psetex"] = PSetEX
|
||||||
cmdMap["mset"] = MSet
|
cmdMap["mset"] = MSet
|
||||||
cmdMap["mget"] = MGet
|
cmdMap["mget"] = MGet
|
||||||
|
cmdMap["msetnx"] = MSetNX
|
||||||
cmdMap["get"] = Get
|
cmdMap["get"] = Get
|
||||||
cmdMap["del"] = Del
|
cmdMap["del"] = Del
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ func MakeCmdMap()map[string]CmdFunc {
|
|||||||
func MakeDB() *DB {
|
func MakeDB() *DB {
|
||||||
return &DB{
|
return &DB{
|
||||||
Data: dict.Make(1024),
|
Data: dict.Make(1024),
|
||||||
|
Locks: &lock.LockMap{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,3 +101,25 @@ func (db *DB)Exec(args [][]byte)(result redis.Reply) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB)Remove(key string) {
|
||||||
|
db.Data.Remove(key)
|
||||||
|
db.Locks.Clean(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB)Removes(keys ...string)(deleted int) {
|
||||||
|
db.Locks.Locks(keys...)
|
||||||
|
defer func() {
|
||||||
|
db.Locks.UnLocks(keys...)
|
||||||
|
db.Locks.Cleans(keys...)
|
||||||
|
}()
|
||||||
|
deleted = 0
|
||||||
|
for _, key := range keys {
|
||||||
|
_, exists := db.Data.Get(key)
|
||||||
|
if exists {
|
||||||
|
db.Data.Remove(key)
|
||||||
|
deleted++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Get(db *DB, args [][]byte)redis.Reply {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return reply.MakeErrReply("ERR wrong number of arguments for 'get' command")
|
|
||||||
}
|
|
||||||
key := string(args[0])
|
|
||||||
val, ok := db.Data.Get(key)
|
|
||||||
if !ok {
|
|
||||||
return &reply.NullBulkReply{}
|
|
||||||
}
|
|
||||||
entity, _ := val.(*DataEntity)
|
|
||||||
if entity.Code == StringCode {
|
|
||||||
bytes, ok := entity.Data.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return &reply.UnknownErrReply{}
|
|
||||||
}
|
|
||||||
return reply.MakeBulkReply(bytes)
|
|
||||||
} else {
|
|
||||||
return &reply.WrongTypeErrReply{}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,14 +14,6 @@ func Del(db *DB, args [][]byte)redis.Reply {
|
|||||||
keys[i] = string(v)
|
keys[i] = string(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleted := 0
|
deleted := db.Removes(keys...)
|
||||||
for _, key := range keys {
|
|
||||||
_, exists := db.Data.Get(key)
|
|
||||||
if exists {
|
|
||||||
db.Data.Remove(key)
|
|
||||||
deleted++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reply.MakeIntReply(int64(deleted))
|
return reply.MakeIntReply(int64(deleted))
|
||||||
}
|
}
|
@@ -1,49 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
476
src/db/list.go
Normal file
476
src/db/list.go
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
// get entity
|
||||||
|
rawEntity, exists := db.Data.Get(key)
|
||||||
|
var entity *DataEntity
|
||||||
|
if !exists {
|
||||||
|
return &reply.NullBulkReply{}
|
||||||
|
} else {
|
||||||
|
entity, _ = rawEntity.(*DataEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
if entity.Code != ListCode {
|
||||||
|
return &reply.WrongTypeErrReply{}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, _ := entity.Data.(*List.LinkedList)
|
||||||
|
size := int64(list.Len())
|
||||||
|
return reply.MakeIntReply(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// get data
|
||||||
|
rawEntity, exists := db.Data.Get(key)
|
||||||
|
var entity *DataEntity
|
||||||
|
if !exists {
|
||||||
|
return &reply.NullBulkReply{}
|
||||||
|
} else {
|
||||||
|
entity, _ = rawEntity.(*DataEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
if entity.Code != ListCode {
|
||||||
|
return &reply.WrongTypeErrReply{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove
|
||||||
|
list, _ := entity.Data.(*List.LinkedList)
|
||||||
|
val, _ := list.Remove(0).([]byte)
|
||||||
|
if list.Len() == 0 {
|
||||||
|
db.Remove(key)
|
||||||
|
}
|
||||||
|
return reply.MakeBulkReply(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
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:]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check type
|
||||||
|
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:]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.MakeIntReply(int64(removed))
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, _ := entity.Data.(*List.LinkedList)
|
||||||
|
val, _ := list.RemoveLast().([]byte)
|
||||||
|
if list.Len() == 0 {
|
||||||
|
db.Remove(key)
|
||||||
|
}
|
||||||
|
return reply.MakeBulkReply(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Locks(sourceKey, destKey)
|
||||||
|
defer db.Locks.UnLocks(sourceKey, destKey)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// pop and push
|
||||||
|
val, _ := sourceList.RemoveLast().([]byte)
|
||||||
|
destList.Insert(0, val)
|
||||||
|
|
||||||
|
if sourceList.Len() == 0 {
|
||||||
|
db.Remove(sourceKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.MakeBulkReply(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RPush(db *DB, args [][]byte)redis.Reply {
|
||||||
|
// parse args
|
||||||
|
if len(args) < 2 {
|
||||||
|
return reply.MakeErrReply("ERR wrong number of arguments for 'rpush' command")
|
||||||
|
}
|
||||||
|
key := string(args[0])
|
||||||
|
values := args[1:]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:]
|
||||||
|
|
||||||
|
// lock
|
||||||
|
db.Locks.Lock(key)
|
||||||
|
defer db.Locks.UnLock(key)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()))
|
||||||
|
}
|
@@ -1,34 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@@ -1,73 +0,0 @@
|
|||||||
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()))
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@@ -1,52 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
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{}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MGet(db *DB, args [][]byte)redis.Reply {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return reply.MakeErrReply("ERR wrong number of arguments for 'mget' command")
|
|
||||||
}
|
|
||||||
keys := make([]string, len(args))
|
|
||||||
for i, v := range args {
|
|
||||||
keys[i] = string(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([][]byte, len(args))
|
|
||||||
for i, key := range keys {
|
|
||||||
val, exists := db.Data.Get(key)
|
|
||||||
if !exists {
|
|
||||||
result[i] = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
entity, _ := val.(*DataEntity)
|
|
||||||
if entity.Code != StringCode {
|
|
||||||
result[i] = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bytes, _ := entity.Data.([]byte)
|
|
||||||
result[i] = bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
return reply.MakeMultiBulkReply(result)
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func MSet(db *DB, args [][]byte)redis.Reply {
|
|
||||||
if len(args) % 2 != 0 || len(args) == 0 {
|
|
||||||
return reply.MakeErrReply("ERR wrong number of arguments for 'mset' command")
|
|
||||||
}
|
|
||||||
size := len(args) / 2
|
|
||||||
entities := make([]*DataEntityWithKey, size)
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
key := string(args[2 * i])
|
|
||||||
value := args[2 * i + 1]
|
|
||||||
entity := &DataEntityWithKey{
|
|
||||||
DataEntity: DataEntity{
|
|
||||||
Code: StringCode,
|
|
||||||
Data: value,
|
|
||||||
},
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
entities[i] = entity
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entity := range entities {
|
|
||||||
db.Data.Put(entity.Key, &entity.DataEntity)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &reply.OkReply{}
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@@ -1,72 +0,0 @@
|
|||||||
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()))
|
|
||||||
}
|
|
@@ -1,8 +1,8 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/HDT3213/godis/src/redis/reply"
|
|
||||||
"github.com/HDT3213/godis/src/interface/redis"
|
"github.com/HDT3213/godis/src/interface/redis"
|
||||||
|
"github.com/HDT3213/godis/src/redis/reply"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Ping(db *DB, args [][]byte)redis.Reply {
|
func Ping(db *DB, args [][]byte)redis.Reply {
|
||||||
@@ -13,4 +13,4 @@ func Ping(db *DB, args [][]byte)redis.Reply {
|
|||||||
} else {
|
} else {
|
||||||
return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
|
return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,6 +7,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Get(db *DB, args [][]byte)redis.Reply {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return reply.MakeErrReply("ERR wrong number of arguments for 'get' command")
|
||||||
|
}
|
||||||
|
key := string(args[0])
|
||||||
|
val, ok := db.Data.Get(key)
|
||||||
|
if !ok {
|
||||||
|
return &reply.NullBulkReply{}
|
||||||
|
}
|
||||||
|
entity, _ := val.(*DataEntity)
|
||||||
|
if entity.Code == StringCode {
|
||||||
|
bytes, ok := entity.Data.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return &reply.UnknownErrReply{}
|
||||||
|
}
|
||||||
|
return reply.MakeBulkReply(bytes)
|
||||||
|
} else {
|
||||||
|
return &reply.WrongTypeErrReply{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
upsertPolicy = iota // default
|
upsertPolicy = iota // default
|
||||||
insertPolicy // set nx
|
insertPolicy // set nx
|
||||||
@@ -156,4 +177,98 @@ func PSetEX(db *DB, args [][]byte)redis.Reply {
|
|||||||
}
|
}
|
||||||
db.Data.PutIfExists(key, entity)
|
db.Data.PutIfExists(key, entity)
|
||||||
return &reply.OkReply{}
|
return &reply.OkReply{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MSet(db *DB, args [][]byte)redis.Reply {
|
||||||
|
if len(args) % 2 != 0 || len(args) == 0 {
|
||||||
|
return reply.MakeErrReply("ERR wrong number of arguments for 'mset' command")
|
||||||
|
}
|
||||||
|
size := len(args) / 2
|
||||||
|
entities := make([]*DataEntityWithKey, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
key := string(args[2 * i])
|
||||||
|
value := args[2 * i + 1]
|
||||||
|
entity := &DataEntityWithKey{
|
||||||
|
DataEntity: DataEntity{
|
||||||
|
Code: StringCode,
|
||||||
|
Data: value,
|
||||||
|
},
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
entities[i] = entity
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entity := range entities {
|
||||||
|
db.Data.Put(entity.Key, &entity.DataEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &reply.OkReply{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MGet(db *DB, args [][]byte)redis.Reply {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return reply.MakeErrReply("ERR wrong number of arguments for 'mget' command")
|
||||||
|
}
|
||||||
|
keys := make([]string, len(args))
|
||||||
|
for i, v := range args {
|
||||||
|
keys[i] = string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([][]byte, len(args))
|
||||||
|
for i, key := range keys {
|
||||||
|
val, exists := db.Data.Get(key)
|
||||||
|
if !exists {
|
||||||
|
result[i] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entity, _ := val.(*DataEntity)
|
||||||
|
if entity.Code != StringCode {
|
||||||
|
result[i] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bytes, _ := entity.Data.([]byte)
|
||||||
|
result[i] = bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.MakeMultiBulkReply(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MSetNX(db *DB, args [][]byte)redis.Reply {
|
||||||
|
// parse args
|
||||||
|
if len(args) % 2 != 0 || len(args) == 0 {
|
||||||
|
return reply.MakeErrReply("ERR wrong number of arguments for 'msetnx' command")
|
||||||
|
}
|
||||||
|
size := len(args) / 2
|
||||||
|
entities := make([]*DataEntityWithKey, size)
|
||||||
|
keys := make([]string, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
key := string(args[2 * i])
|
||||||
|
value := args[2 * i + 1]
|
||||||
|
entity := &DataEntityWithKey{
|
||||||
|
DataEntity: DataEntity{
|
||||||
|
Code: StringCode,
|
||||||
|
Data: value,
|
||||||
|
},
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
entities[i] = entity
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock keys
|
||||||
|
db.Locks.Locks(keys...)
|
||||||
|
defer db.Locks.UnLocks(keys...)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
_, exists := db.Data.Get(key)
|
||||||
|
if exists {
|
||||||
|
return reply.MakeIntReply(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entity := range entities {
|
||||||
|
db.Data.Put(entity.Key, &entity.DataEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.MakeIntReply(1)
|
||||||
}
|
}
|
Reference in New Issue
Block a user