mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-09-27 04:16:06 +08:00
255 lines
7.4 KiB
Go
255 lines
7.4 KiB
Go
// Copyright 2024 Kelvin Clement Mwinuka
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package str
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/echovault/sugardb/internal"
|
|
"github.com/echovault/sugardb/internal/constants"
|
|
)
|
|
|
|
func handleSetRange(params internal.HandlerFuncParams) ([]byte, error) {
|
|
keys, err := setRangeKeyFunc(params.Command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := keys.WriteKeys[0]
|
|
keyExists := params.KeysExist(params.Context, keys.WriteKeys)[key]
|
|
|
|
offset, ok := internal.AdaptType(params.Command[2]).(int)
|
|
if !ok {
|
|
return nil, errors.New("offset must be an integer")
|
|
}
|
|
|
|
newStr := params.Command[3]
|
|
|
|
if !keyExists {
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil
|
|
}
|
|
|
|
str, ok := params.GetValues(params.Context, []string{key})[key].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("value at key %s is not a string", key)
|
|
}
|
|
|
|
// If the offset >= length of the string, append the new string to the old one.
|
|
if offset >= len(str) {
|
|
newStr = str + newStr
|
|
if err = params.SetValues(params.Context, map[string]interface{}{key: newStr}); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil
|
|
}
|
|
|
|
// If the offset is < 0, prepend the new string to the old one.
|
|
if offset < 0 {
|
|
newStr = newStr + str
|
|
if err = params.SetValues(params.Context, map[string]interface{}{key: newStr}); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(newStr))), nil
|
|
}
|
|
|
|
strRunes := []rune(str)
|
|
|
|
for i := 0; i < len(newStr); i++ {
|
|
// If we're still withing the length of the original string, replace the rune in strRunes
|
|
if offset < len(str) {
|
|
strRunes[offset] = rune(newStr[i])
|
|
offset += 1
|
|
continue
|
|
}
|
|
// We are past the length of the original string, append the remainder of newStr to strRunes
|
|
strRunes = append(strRunes, []rune(newStr)[i:]...)
|
|
break
|
|
}
|
|
|
|
if err = params.SetValues(params.Context, map[string]interface{}{key: string(strRunes)}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(strRunes))), nil
|
|
}
|
|
|
|
func handleStrLen(params internal.HandlerFuncParams) ([]byte, error) {
|
|
keys, err := strLenKeyFunc(params.Command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := keys.ReadKeys[0]
|
|
keyExists := params.KeysExist(params.Context, keys.ReadKeys)[key]
|
|
|
|
if !keyExists {
|
|
return []byte(":0\r\n"), nil
|
|
}
|
|
|
|
value, ok := params.GetValues(params.Context, []string{key})[key].(string)
|
|
|
|
if !ok {
|
|
return nil, fmt.Errorf("value at key %s is not a string", key)
|
|
}
|
|
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(value))), nil
|
|
}
|
|
|
|
func handleSubStr(params internal.HandlerFuncParams) ([]byte, error) {
|
|
keys, err := subStrKeyFunc(params.Command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := keys.ReadKeys[0]
|
|
keyExists := params.KeysExist(params.Context, keys.ReadKeys)[key]
|
|
|
|
start, startOk := internal.AdaptType(params.Command[2]).(int)
|
|
end, endOk := internal.AdaptType(params.Command[3]).(int)
|
|
reversed := false
|
|
|
|
if !startOk || !endOk {
|
|
return nil, errors.New("start and end indices must be integers")
|
|
}
|
|
|
|
if !keyExists {
|
|
return nil, fmt.Errorf("key %s does not exist", key)
|
|
}
|
|
|
|
value, ok := params.GetValues(params.Context, []string{key})[key].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("value at key %s is not a string", key)
|
|
}
|
|
|
|
if start < 0 {
|
|
start = len(value) - internal.AbsInt(start)
|
|
}
|
|
if end < 0 {
|
|
end = len(value) - internal.AbsInt(end)
|
|
}
|
|
|
|
if end >= 0 && end >= start {
|
|
end += 1
|
|
}
|
|
|
|
if end > len(value) {
|
|
end = len(value)
|
|
}
|
|
|
|
if start > end {
|
|
reversed = true
|
|
start, end = end, start
|
|
}
|
|
|
|
str := value[start:end]
|
|
|
|
if reversed {
|
|
res := ""
|
|
for i := len(str) - 1; i >= 0; i-- {
|
|
res = res + string(str[i])
|
|
}
|
|
str = res
|
|
}
|
|
|
|
return []byte(fmt.Sprintf("$%d\r\n%s\r\n", len(str), str)), nil
|
|
}
|
|
|
|
func handleAppend(params internal.HandlerFuncParams) ([]byte, error) {
|
|
keys, err := appendKeyFunc(params.Command)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := keys.WriteKeys[0]
|
|
keyExists := params.KeysExist(params.Context, keys.WriteKeys)[key]
|
|
value := params.Command[2]
|
|
if !keyExists {
|
|
if err = params.SetValues(params.Context, map[string]interface{}{
|
|
key: internal.AdaptType(value),
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(value))), nil
|
|
}
|
|
currentValue, ok := params.GetValues(params.Context, []string{key})[key].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Value at key %s is not a string", key)
|
|
}
|
|
newValue := fmt.Sprintf("%v%s", currentValue, value)
|
|
if err = params.SetValues(params.Context, map[string]interface{}{
|
|
key: internal.AdaptType(newValue),
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return []byte(fmt.Sprintf(":%d\r\n", len(newValue))), nil
|
|
}
|
|
|
|
func Commands() []internal.Command {
|
|
return []internal.Command{
|
|
{
|
|
Command: "setrange",
|
|
Module: constants.StringModule,
|
|
Categories: []string{constants.StringCategory, constants.WriteCategory, constants.SlowCategory},
|
|
Description: `(SETRANGE key offset value)
|
|
Overwrites part of a string value with another by offset. Creates the key if it doesn't exist.`,
|
|
Sync: true,
|
|
Type: "BUILT_IN",
|
|
KeyExtractionFunc: setRangeKeyFunc,
|
|
HandlerFunc: handleSetRange,
|
|
},
|
|
{
|
|
Command: "strlen",
|
|
Module: constants.StringModule,
|
|
Categories: []string{constants.StringCategory, constants.ReadCategory, constants.FastCategory},
|
|
Description: "(STRLEN key) Returns length of the key's value if it's a string.",
|
|
Sync: false,
|
|
Type: "BUILT_IN",
|
|
KeyExtractionFunc: strLenKeyFunc,
|
|
HandlerFunc: handleStrLen,
|
|
},
|
|
{
|
|
Command: "substr",
|
|
Module: constants.StringModule,
|
|
Categories: []string{constants.StringCategory, constants.ReadCategory, constants.SlowCategory},
|
|
Description: "(SUBSTR key start end) Returns a substring from the string value.",
|
|
Sync: false,
|
|
Type: "BUILT_IN",
|
|
KeyExtractionFunc: subStrKeyFunc,
|
|
HandlerFunc: handleSubStr,
|
|
},
|
|
{
|
|
Command: "getrange",
|
|
Module: constants.StringModule,
|
|
Categories: []string{constants.StringCategory, constants.ReadCategory, constants.SlowCategory},
|
|
Description: "(GETRANGE key start end) Returns a substring from the string value.",
|
|
Sync: false,
|
|
Type: "BUILT_IN",
|
|
KeyExtractionFunc: subStrKeyFunc,
|
|
HandlerFunc: handleSubStr,
|
|
},
|
|
{
|
|
Command: "append",
|
|
Module: constants.StringModule,
|
|
Categories: []string{constants.StringCategory, constants.WriteCategory, constants.SlowCategory},
|
|
Description: `(APPEND key value) If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to [SET] in this special case.`,
|
|
Sync: true,
|
|
Type: "BUILT_IN",
|
|
KeyExtractionFunc: appendKeyFunc,
|
|
HandlerFunc: handleAppend,
|
|
},
|
|
}
|
|
}
|