mirror of
https://github.com/HDT3213/godis.git
synced 2025-09-27 13:12:19 +08:00
888 lines
24 KiB
Go
888 lines
24 KiB
Go
package database
|
|
|
|
import (
|
|
"math/bits"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hdt3213/godis/aof"
|
|
"github.com/hdt3213/godis/datastruct/bitmap"
|
|
"github.com/hdt3213/godis/interface/database"
|
|
"github.com/hdt3213/godis/interface/redis"
|
|
"github.com/hdt3213/godis/lib/utils"
|
|
"github.com/hdt3213/godis/redis/protocol"
|
|
)
|
|
|
|
func (db *DB) getAsString(key string) ([]byte, protocol.ErrorReply) {
|
|
entity, ok := db.GetEntity(key)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
bytes, ok := entity.Data.([]byte)
|
|
if !ok {
|
|
return nil, &protocol.WrongTypeErrReply{}
|
|
}
|
|
return bytes, nil
|
|
}
|
|
|
|
// execGet returns string value bound to the given key
|
|
func execGet(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes == nil {
|
|
return &protocol.NullBulkReply{}
|
|
}
|
|
return protocol.MakeBulkReply(bytes)
|
|
}
|
|
|
|
const (
|
|
upsertPolicy = iota // default
|
|
insertPolicy // set nx
|
|
updatePolicy // set ex
|
|
)
|
|
|
|
const unlimitedTTL int64 = 0
|
|
|
|
// execGetEX Get the value of key and optionally set its expiration
|
|
func execGetEX(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bytes, err := db.getAsString(key)
|
|
ttl := unlimitedTTL
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes == nil {
|
|
return &protocol.NullBulkReply{}
|
|
}
|
|
|
|
for i := 1; i < len(args); i++ {
|
|
arg := strings.ToUpper(string(args[i]))
|
|
if arg == "EX" { // ttl in seconds
|
|
if ttl != unlimitedTTL {
|
|
// ttl has been set
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if i+1 >= len(args) {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
ttlArg, err := strconv.ParseInt(string(args[i+1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in getex")
|
|
}
|
|
ttl = ttlArg * 1000
|
|
i++ // skip next arg
|
|
} else if arg == "PX" { // ttl in milliseconds
|
|
if ttl != unlimitedTTL {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if i+1 >= len(args) {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
ttlArg, err := strconv.ParseInt(string(args[i+1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in getex")
|
|
}
|
|
ttl = ttlArg
|
|
i++ // skip next arg
|
|
} else if arg == "PERSIST" {
|
|
if ttl != unlimitedTTL { // PERSIST Cannot be used with EX | PX
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if i+1 > len(args) {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
db.Persist(key)
|
|
}
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
if ttl != unlimitedTTL { // EX | PX
|
|
expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
|
|
db.Expire(key, expireTime)
|
|
db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
|
|
} else { // PERSIST
|
|
db.Persist(key) // override ttl
|
|
// we convert to persist command to write aof
|
|
db.addAof(utils.ToCmdLine3("persist", args[0]))
|
|
}
|
|
}
|
|
return protocol.MakeBulkReply(bytes)
|
|
}
|
|
|
|
// execSet sets string value and time to live to the given key
|
|
func execSet(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
value := args[1]
|
|
policy := upsertPolicy
|
|
ttl := unlimitedTTL
|
|
|
|
// parse options
|
|
if len(args) > 2 {
|
|
for i := 2; i < len(args); i++ {
|
|
arg := strings.ToUpper(string(args[i]))
|
|
if arg == "NX" { // insert
|
|
if policy == updatePolicy {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
policy = insertPolicy
|
|
} else if arg == "XX" { // update policy
|
|
if policy == insertPolicy {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
policy = updatePolicy
|
|
} else if arg == "EX" { // ttl in seconds
|
|
if ttl != unlimitedTTL {
|
|
// ttl has been set
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if i+1 >= len(args) {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
ttlArg, err := strconv.ParseInt(string(args[i+1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in set")
|
|
}
|
|
ttl = ttlArg * 1000
|
|
i++ // skip next arg
|
|
} else if arg == "PX" { // ttl in milliseconds
|
|
if ttl != unlimitedTTL {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if i+1 >= len(args) {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
ttlArg, err := strconv.ParseInt(string(args[i+1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in set")
|
|
}
|
|
ttl = ttlArg
|
|
i++ // skip next arg
|
|
} else {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
}
|
|
}
|
|
|
|
entity := &database.DataEntity{
|
|
Data: value,
|
|
}
|
|
|
|
var result int
|
|
switch policy {
|
|
case upsertPolicy:
|
|
db.PutEntity(key, entity)
|
|
result = 1
|
|
case insertPolicy:
|
|
result = db.PutIfAbsent(key, entity)
|
|
case updatePolicy:
|
|
result = db.PutIfExists(key, entity)
|
|
}
|
|
if result > 0 {
|
|
if ttl != unlimitedTTL {
|
|
expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
|
|
db.Expire(key, expireTime)
|
|
db.addAof(CmdLine{
|
|
[]byte("SET"),
|
|
args[0],
|
|
args[1],
|
|
})
|
|
db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
|
|
} else {
|
|
db.Persist(key) // override ttl
|
|
db.addAof(utils.ToCmdLine3("set", args...))
|
|
}
|
|
}
|
|
|
|
if result > 0 {
|
|
return &protocol.OkReply{}
|
|
}
|
|
return &protocol.NullBulkReply{}
|
|
}
|
|
|
|
// execSetNX sets string if not exists
|
|
func execSetNX(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
value := args[1]
|
|
entity := &database.DataEntity{
|
|
Data: value,
|
|
}
|
|
result := db.PutIfAbsent(key, entity)
|
|
db.addAof(utils.ToCmdLine3("setnx", args...))
|
|
return protocol.MakeIntReply(int64(result))
|
|
}
|
|
|
|
// execSetEX sets string and its ttl
|
|
func execSetEX(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
value := args[2]
|
|
|
|
ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in setex")
|
|
}
|
|
ttl := ttlArg * 1000
|
|
|
|
entity := &database.DataEntity{
|
|
Data: value,
|
|
}
|
|
|
|
db.PutEntity(key, entity)
|
|
expireTime := time.Now().Add(time.Duration(ttl) * time.Millisecond)
|
|
db.Expire(key, expireTime)
|
|
db.addAof(utils.ToCmdLine3("setex", args...))
|
|
db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
|
|
return &protocol.OkReply{}
|
|
}
|
|
|
|
// execPSetEX set a key's time to live in milliseconds
|
|
func execPSetEX(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
value := args[2]
|
|
|
|
ttlArg, err := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err != nil {
|
|
return &protocol.SyntaxErrReply{}
|
|
}
|
|
if ttlArg <= 0 {
|
|
return protocol.MakeErrReply("ERR invalid expire time in setex")
|
|
}
|
|
|
|
entity := &database.DataEntity{
|
|
Data: value,
|
|
}
|
|
|
|
db.PutEntity(key, entity)
|
|
expireTime := time.Now().Add(time.Duration(ttlArg) * time.Millisecond)
|
|
db.Expire(key, expireTime)
|
|
db.addAof(utils.ToCmdLine3("setex", args...))
|
|
db.addAof(aof.MakeExpireCmd(key, expireTime).Args)
|
|
|
|
return &protocol.OkReply{}
|
|
}
|
|
|
|
func prepareMSet(args [][]byte) ([]string, []string) {
|
|
size := len(args) / 2
|
|
keys := make([]string, size)
|
|
for i := 0; i < size; i++ {
|
|
keys[i] = string(args[2*i])
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
func undoMSet(db *DB, args [][]byte) []CmdLine {
|
|
writeKeys, _ := prepareMSet(args)
|
|
return rollbackGivenKeys(db, writeKeys...)
|
|
}
|
|
|
|
// execMSet sets multi key-value in database
|
|
func execMSet(db *DB, args [][]byte) redis.Reply {
|
|
if len(args)%2 != 0 {
|
|
return protocol.MakeSyntaxErrReply()
|
|
}
|
|
|
|
size := len(args) / 2
|
|
keys := make([]string, size)
|
|
values := make([][]byte, size)
|
|
for i := 0; i < size; i++ {
|
|
keys[i] = string(args[2*i])
|
|
values[i] = args[2*i+1]
|
|
}
|
|
|
|
for i, key := range keys {
|
|
value := values[i]
|
|
db.PutEntity(key, &database.DataEntity{Data: value})
|
|
}
|
|
db.addAof(utils.ToCmdLine3("mset", args...))
|
|
return &protocol.OkReply{}
|
|
}
|
|
|
|
func prepareMGet(args [][]byte) ([]string, []string) {
|
|
keys := make([]string, len(args))
|
|
for i, v := range args {
|
|
keys[i] = string(v)
|
|
}
|
|
return nil, keys
|
|
}
|
|
|
|
// execMGet get multi key-value from database
|
|
func execMGet(db *DB, args [][]byte) redis.Reply {
|
|
keys := make([]string, len(args))
|
|
for i, v := range args {
|
|
keys[i] = string(v)
|
|
}
|
|
|
|
result := make([][]byte, len(args))
|
|
for i, key := range keys {
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
_, isWrongType := err.(*protocol.WrongTypeErrReply)
|
|
if isWrongType {
|
|
result[i] = nil
|
|
continue
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
result[i] = bytes // nil or []byte
|
|
}
|
|
|
|
return protocol.MakeMultiBulkReply(result)
|
|
}
|
|
|
|
// execMSetNX sets multi key-value in database, only if none of the given keys exist
|
|
func execMSetNX(db *DB, args [][]byte) redis.Reply {
|
|
// parse args
|
|
if len(args)%2 != 0 {
|
|
return protocol.MakeSyntaxErrReply()
|
|
}
|
|
size := len(args) / 2
|
|
values := make([][]byte, size)
|
|
keys := make([]string, size)
|
|
for i := 0; i < size; i++ {
|
|
keys[i] = string(args[2*i])
|
|
values[i] = args[2*i+1]
|
|
}
|
|
|
|
for _, key := range keys {
|
|
_, exists := db.GetEntity(key)
|
|
if exists {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
}
|
|
|
|
for i, key := range keys {
|
|
value := values[i]
|
|
db.PutEntity(key, &database.DataEntity{Data: value})
|
|
}
|
|
db.addAof(utils.ToCmdLine3("msetnx", args...))
|
|
return protocol.MakeIntReply(1)
|
|
}
|
|
|
|
// execGetSet sets value of a string-type key and returns its old value
|
|
func execGetSet(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
value := args[1]
|
|
|
|
old, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
db.PutEntity(key, &database.DataEntity{Data: value})
|
|
db.Persist(key) // override ttl
|
|
db.addAof(utils.ToCmdLine3("set", args...))
|
|
if old == nil {
|
|
return new(protocol.NullBulkReply)
|
|
}
|
|
return protocol.MakeBulkReply(old)
|
|
}
|
|
|
|
// execGetDel Get the value of key and delete the key.
|
|
func execGetDel(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
|
|
old, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if old == nil {
|
|
return new(protocol.NullBulkReply)
|
|
}
|
|
db.Remove(key)
|
|
|
|
// We convert to del command to write aof
|
|
db.addAof(utils.ToCmdLine3("del", args...))
|
|
return protocol.MakeBulkReply(old)
|
|
}
|
|
|
|
// execIncr increments the integer value of a key by one
|
|
func execIncr(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes != nil {
|
|
val, err := strconv.ParseInt(string(bytes), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte(strconv.FormatInt(val+1, 10)),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incr", args...))
|
|
return protocol.MakeIntReply(val + 1)
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte("1"),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incr", args...))
|
|
return protocol.MakeIntReply(1)
|
|
}
|
|
|
|
// execIncrBy increments the integer value of a key by given value
|
|
func execIncrBy(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
rawDelta := string(args[1])
|
|
delta, err := strconv.ParseInt(rawDelta, 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
|
|
bytes, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
if bytes != nil {
|
|
// existed value
|
|
val, err := strconv.ParseInt(string(bytes), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte(strconv.FormatInt(val+delta, 10)),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incrby", args...))
|
|
return protocol.MakeIntReply(val + delta)
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: args[1],
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incrby", args...))
|
|
return protocol.MakeIntReply(delta)
|
|
}
|
|
|
|
// execIncrByFloat increments the float value of a key by given value
|
|
func execIncrByFloat(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
rawDelta := string(args[1])
|
|
delta, err := strconv.ParseFloat(rawDelta, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not a valid float")
|
|
}
|
|
|
|
bytes, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
if bytes != nil {
|
|
val, err := strconv.ParseFloat(string(bytes), 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not a valid float")
|
|
}
|
|
resultBytes := []byte(strconv.FormatFloat(val+delta, 'f', -1, 64))
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: resultBytes,
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incrbyfloat", args...))
|
|
return protocol.MakeBulkReply(resultBytes)
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: args[1],
|
|
})
|
|
db.addAof(utils.ToCmdLine3("incrbyfloat", args...))
|
|
return protocol.MakeBulkReply(args[1])
|
|
}
|
|
|
|
// execDecr decrements the integer value of a key by one
|
|
func execDecr(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
|
|
bytes, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
if bytes != nil {
|
|
val, err := strconv.ParseInt(string(bytes), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte(strconv.FormatInt(val-1, 10)),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("decr", args...))
|
|
return protocol.MakeIntReply(val - 1)
|
|
}
|
|
entity := &database.DataEntity{
|
|
Data: []byte("-1"),
|
|
}
|
|
db.PutEntity(key, entity)
|
|
db.addAof(utils.ToCmdLine3("decr", args...))
|
|
return protocol.MakeIntReply(-1)
|
|
}
|
|
|
|
// execDecrBy decrements the integer value of a key by onedecrement
|
|
func execDecrBy(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
rawDelta := string(args[1])
|
|
delta, err := strconv.ParseInt(rawDelta, 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
|
|
bytes, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
if bytes != nil {
|
|
val, err := strconv.ParseInt(string(bytes), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte(strconv.FormatInt(val-delta, 10)),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("decrby", args...))
|
|
return protocol.MakeIntReply(val - delta)
|
|
}
|
|
valueStr := strconv.FormatInt(-delta, 10)
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: []byte(valueStr),
|
|
})
|
|
db.addAof(utils.ToCmdLine3("decrby", args...))
|
|
return protocol.MakeIntReply(-delta)
|
|
}
|
|
|
|
// execStrLen returns len of string value bound to the given key
|
|
func execStrLen(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes == nil {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
return protocol.MakeIntReply(int64(len(bytes)))
|
|
}
|
|
|
|
// execAppend sets string value to the given key
|
|
func execAppend(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bytes = append(bytes, args[1]...)
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: bytes,
|
|
})
|
|
db.addAof(utils.ToCmdLine3("append", args...))
|
|
return protocol.MakeIntReply(int64(len(bytes)))
|
|
}
|
|
|
|
// execSetRange overwrites part of the string stored at key, starting at the specified offset.
|
|
// If the offset is larger than the current length of the string at key, the string is padded with zero-bytes.
|
|
func execSetRange(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
offset, errNative := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if errNative != nil {
|
|
return protocol.MakeErrReply(errNative.Error())
|
|
}
|
|
value := args[2]
|
|
bytes, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bytesLen := int64(len(bytes))
|
|
if bytesLen < offset {
|
|
diff := offset - bytesLen
|
|
diffArray := make([]byte, diff)
|
|
bytes = append(bytes, diffArray...)
|
|
bytesLen = int64(len(bytes))
|
|
}
|
|
for i := 0; i < len(value); i++ {
|
|
idx := offset + int64(i)
|
|
if idx >= bytesLen {
|
|
bytes = append(bytes, value[i])
|
|
} else {
|
|
bytes[idx] = value[i]
|
|
}
|
|
}
|
|
db.PutEntity(key, &database.DataEntity{
|
|
Data: bytes,
|
|
})
|
|
db.addAof(utils.ToCmdLine3("setRange", args...))
|
|
return protocol.MakeIntReply(int64(len(bytes)))
|
|
}
|
|
|
|
func execGetRange(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
startIdx, err2 := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
endIdx, err2 := strconv.ParseInt(string(args[2]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
|
|
bs, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bs == nil {
|
|
return protocol.MakeNullBulkReply()
|
|
}
|
|
bytesLen := int64(len(bs))
|
|
beg, end := utils.ConvertRange(startIdx, endIdx, bytesLen)
|
|
if beg < 0 {
|
|
return protocol.MakeNullBulkReply()
|
|
}
|
|
return protocol.MakeBulkReply(bs[beg:end])
|
|
}
|
|
|
|
func execSetBit(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
offset, err := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR bit offset is not an integer or out of range")
|
|
}
|
|
valStr := string(args[2])
|
|
var v byte
|
|
if valStr == "1" {
|
|
v = 1
|
|
} else if valStr == "0" {
|
|
v = 0
|
|
} else {
|
|
return protocol.MakeErrReply("ERR bit is not an integer or out of range")
|
|
}
|
|
bs, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
bm := bitmap.FromBytes(bs)
|
|
former := bm.GetBit(offset)
|
|
bm.SetBit(offset, v)
|
|
db.PutEntity(key, &database.DataEntity{Data: bm.ToBytes()})
|
|
db.addAof(utils.ToCmdLine3("setBit", args...))
|
|
return protocol.MakeIntReply(int64(former))
|
|
}
|
|
|
|
func execGetBit(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
offset, err := strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err != nil {
|
|
return protocol.MakeErrReply("ERR bit offset is not an integer or out of range")
|
|
}
|
|
bs, errReply := db.getAsString(key)
|
|
if errReply != nil {
|
|
return errReply
|
|
}
|
|
if bs == nil {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
bm := bitmap.FromBytes(bs)
|
|
return protocol.MakeIntReply(int64(bm.GetBit(offset)))
|
|
}
|
|
|
|
func execBitCount(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bs, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bs == nil {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
byteMode := true
|
|
if len(args) > 3 {
|
|
mode := strings.ToLower(string(args[3]))
|
|
if mode == "bit" {
|
|
byteMode = false
|
|
} else if mode == "byte" {
|
|
byteMode = true
|
|
} else {
|
|
return protocol.MakeErrReply("ERR syntax error")
|
|
}
|
|
}
|
|
var size int64
|
|
bm := bitmap.FromBytes(bs)
|
|
if byteMode {
|
|
size = int64(len(*bm))
|
|
} else {
|
|
size = int64(bm.BitSize())
|
|
}
|
|
var beg, end int
|
|
if len(args) > 1 {
|
|
var err2 error
|
|
var startIdx, endIdx int64
|
|
startIdx, err2 = strconv.ParseInt(string(args[1]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
endIdx, err2 = strconv.ParseInt(string(args[2]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
beg, end = utils.ConvertRange(startIdx, endIdx, size)
|
|
if beg < 0 {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
}
|
|
var count int64
|
|
if byteMode {
|
|
bm.ForEachByte(beg, end, func(offset int64, val byte) bool {
|
|
count += int64(bits.OnesCount8(val))
|
|
return true
|
|
})
|
|
} else {
|
|
bm.ForEachBit(int64(beg), int64(end), func(offset int64, val byte) bool {
|
|
if val > 0 {
|
|
count++
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
return protocol.MakeIntReply(count)
|
|
}
|
|
|
|
func execBitPos(db *DB, args [][]byte) redis.Reply {
|
|
key := string(args[0])
|
|
bs, err := db.getAsString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bs == nil {
|
|
return protocol.MakeIntReply(-1)
|
|
}
|
|
valStr := string(args[1])
|
|
var v byte
|
|
if valStr == "1" {
|
|
v = 1
|
|
} else if valStr == "0" {
|
|
v = 0
|
|
} else {
|
|
return protocol.MakeErrReply("ERR bit is not an integer or out of range")
|
|
}
|
|
byteMode := true
|
|
if len(args) > 4 {
|
|
mode := strings.ToLower(string(args[4]))
|
|
if mode == "bit" {
|
|
byteMode = false
|
|
} else if mode == "byte" {
|
|
byteMode = true
|
|
} else {
|
|
return protocol.MakeErrReply("ERR syntax error")
|
|
}
|
|
}
|
|
var size int64
|
|
bm := bitmap.FromBytes(bs)
|
|
if byteMode {
|
|
size = int64(len(*bm))
|
|
} else {
|
|
size = int64(bm.BitSize())
|
|
}
|
|
var beg, end int
|
|
if len(args) > 2 {
|
|
var err2 error
|
|
var startIdx, endIdx int64
|
|
startIdx, err2 = strconv.ParseInt(string(args[2]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
endIdx, err2 = strconv.ParseInt(string(args[3]), 10, 64)
|
|
if err2 != nil {
|
|
return protocol.MakeErrReply("ERR value is not an integer or out of range")
|
|
}
|
|
beg, end = utils.ConvertRange(startIdx, endIdx, size)
|
|
if beg < 0 {
|
|
return protocol.MakeIntReply(0)
|
|
}
|
|
}
|
|
if byteMode {
|
|
beg *= 8
|
|
end *= 8
|
|
}
|
|
var offset = int64(-1)
|
|
bm.ForEachBit(int64(beg), int64(end), func(o int64, val byte) bool {
|
|
if val == v {
|
|
offset = o
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
return protocol.MakeIntReply(offset)
|
|
}
|
|
|
|
// GetRandomKey Randomly return (do not delete) a key from the godis
|
|
func getRandomKey(db *DB, args [][]byte) redis.Reply {
|
|
k := db.data.RandomKeys(1)
|
|
if len(k) == 0 {
|
|
return &protocol.NullBulkReply{}
|
|
}
|
|
return protocol.MakeBulkReply([]byte(k[0]))
|
|
}
|
|
|
|
func init() {
|
|
registerCommand("Set", execSet, writeFirstKey, rollbackFirstKey, -3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("SetNx", execSetNX, writeFirstKey, rollbackFirstKey, 3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("SetEX", execSetEX, writeFirstKey, rollbackFirstKey, 4, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("PSetEX", execPSetEX, writeFirstKey, rollbackFirstKey, 4, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("MSet", execMSet, prepareMSet, undoMSet, -3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, -1, 2)
|
|
registerCommand("MGet", execMGet, prepareMGet, nil, -2, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("MSetNX", execMSetNX, prepareMSet, undoMSet, -3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("Get", execGet, readFirstKey, nil, 2, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("GetEX", execGetEX, writeFirstKey, rollbackFirstKey, -2, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("GetSet", execGetSet, writeFirstKey, rollbackFirstKey, 3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("GetDel", execGetDel, writeFirstKey, rollbackFirstKey, 2, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("Incr", execIncr, writeFirstKey, rollbackFirstKey, 2, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("IncrBy", execIncrBy, writeFirstKey, rollbackFirstKey, 3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("IncrByFloat", execIncrByFloat, writeFirstKey, rollbackFirstKey, 3, flagWrite).attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("Decr", execDecr, writeFirstKey, rollbackFirstKey, 2, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("DecrBy", execDecrBy, writeFirstKey, rollbackFirstKey, 3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("StrLen", execStrLen, readFirstKey, nil, 2, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("Append", execAppend, writeFirstKey, rollbackFirstKey, 3, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("SetRange", execSetRange, writeFirstKey, rollbackFirstKey, 4, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("GetRange", execGetRange, readFirstKey, nil, 4, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
|
registerCommand("SetBit", execSetBit, writeFirstKey, rollbackFirstKey, 4, flagWrite).
|
|
attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM}, 1, 1, 1)
|
|
registerCommand("GetBit", execGetBit, readFirstKey, nil, 3, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
|
|
registerCommand("BitCount", execBitCount, readFirstKey, nil, -2, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
|
registerCommand("BitPos", execBitPos, readFirstKey, nil, -3, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
|
|
registerCommand("Randomkey", getRandomKey, readAllKeys, nil, 1, flagReadOnly).
|
|
attachCommandExtra([]string{redisFlagReadonly, redisFlagRandom}, 1, 1, 1)
|
|
}
|