Implemented LPUSH, RPUSH, and LRANGE commands

This commit is contained in:
Kelvin Clement Mwinuka
2023-07-05 07:10:59 +08:00
parent f744ed8a06
commit 2ea87c64f3
5 changed files with 228 additions and 23 deletions

View File

@@ -36,15 +36,19 @@ type Server struct {
plugins []Plugin
}
func (server *Server) GetData(key string) interface{} {
func (server *Server) Lock() {
server.data.mu.Lock()
defer server.data.mu.Unlock()
}
func (Server *Server) Unlock() {
Server.data.mu.Unlock()
}
func (server *Server) GetData(key string) interface{} {
return server.data.data[key]
}
func (server *Server) SetData(key string, value interface{}) {
server.data.mu.Lock()
defer server.data.mu.Unlock()
server.data.data[key] = value
}

View File

@@ -2,9 +2,20 @@ package main
import (
"bufio"
"fmt"
"math"
"strings"
"github.com/kelvinmwinuka/memstore/utils"
)
const (
OK = "+OK\r\n\n"
)
type Server interface {
Lock()
Unlock()
GetData(key string) interface{}
SetData(key string, value interface{})
}
@@ -30,6 +41,182 @@ func (p *plugin) Description() string {
}
func (p *plugin) HandleCommand(cmd []string, server interface{}, conn *bufio.Writer) {
switch strings.ToLower(cmd[0]) {
case "lrange":
handleLRange(cmd, server.(Server), conn)
case "lpush":
handleLPush(cmd, server.(Server), conn)
case "rpush":
handleRPush(cmd, server.(Server), conn)
}
}
func handleLRange(cmd []string, server Server, conn *bufio.Writer) {
if len(cmd) != 4 {
conn.Write([]byte("-Error wrong number of arguments for LRANGE command\r\n\n"))
conn.Flush()
return
}
start, startOk := utils.AdaptType(cmd[2]).(int)
end, endOk := utils.AdaptType(cmd[3]).(int)
if !startOk || !endOk {
conn.Write([]byte("-Error both start and end indices must be integers\r\n\n"))
conn.Flush()
return
}
server.Lock()
list, ok := server.GetData(cmd[1]).([]interface{})
server.Unlock()
if !ok {
conn.Write([]byte("-Error type cannot be returned with LRANGE command\r\n\n"))
conn.Flush()
return
}
// Make sure start is within range
if !(start >= 0 && start < len(list)) {
conn.Write([]byte("-Error start index not within list range\r\n\n"))
conn.Flush()
return
}
// Make sure end is within range, or is -1 otherwise
if !((end >= 0 && end < len(list)) || end == -1) {
conn.Write([]byte("-Error end index must be within list range or -1\r\n\n"))
conn.Flush()
return
}
// If end is -1, read list from start to the end of the list
if end == -1 {
conn.Write([]byte("*" + fmt.Sprint(len(list)-start) + "\r\n"))
for i := start; i < len(list); i++ {
str := fmt.Sprintf("%v", list[i])
conn.Write([]byte("$" + fmt.Sprint(len(str)) + "\r\n" + str + "\r\n"))
}
conn.Write([]byte("\n"))
conn.Flush()
return
}
// Make sure start and end are not equal to each other
if start == end {
conn.Write([]byte("-Error start and end indices cannot be equal equal\r\n\n"))
conn.Flush()
return
}
// If end is not -1:
// 1) If end is larger than start, return slice from start -> end
// 2) If end is smaller than start, return slice from end -> start
conn.Write([]byte("*" + fmt.Sprint(int(math.Abs(float64(start-end)))+1) + "\r\n"))
i := start
j := end + 1
if start > end {
j = end - 1
}
for i != j {
str := fmt.Sprintf("%v", list[i])
conn.Write([]byte("$" + fmt.Sprint(len(str)) + "\r\n" + str + "\r\n"))
if start < end {
i++
} else {
i--
}
}
conn.Write([]byte("\n"))
conn.Flush()
}
func handleLPush(cmd []string, server Server, conn *bufio.Writer) {
if len(cmd) < 3 {
conn.Write([]byte("-Error wrong number of arguments for LPUSH command\r\n\n"))
conn.Flush()
return
}
server.Lock()
newElems := []interface{}{}
for _, elem := range cmd[2:] {
newElems = append(newElems, utils.AdaptType(elem))
}
currentList := server.GetData(cmd[1])
if currentList == nil {
server.SetData(cmd[1], newElems)
server.Unlock()
conn.Write([]byte(OK))
conn.Flush()
return
}
l, ok := currentList.([]interface{})
if !ok {
server.Unlock()
conn.Write([]byte("-Error LPUSH command on non-list item\r\n\n"))
conn.Flush()
return
}
server.SetData(cmd[1], append(newElems, l...))
server.Unlock()
conn.Write([]byte(OK))
conn.Flush()
}
func handleRPush(cmd []string, server Server, conn *bufio.Writer) {
if len(cmd) < 3 {
conn.Write([]byte("-Error wrong number of arguments for LPUSH command\r\n\n"))
conn.Flush()
return
}
server.Lock()
newElems := []interface{}{}
for _, elem := range cmd[2:] {
newElems = append(newElems, utils.AdaptType(elem))
}
currentList := server.GetData(cmd[1])
if currentList == nil {
server.SetData(cmd[1], newElems)
server.Unlock()
conn.Write([]byte(OK))
conn.Flush()
return
}
l, ok := currentList.([]interface{})
if !ok {
server.Unlock()
conn.Write([]byte("-Error RPUSH command on non-list item\r\n\n"))
conn.Flush()
return
}
server.SetData(cmd[1], append(l, newElems...))
server.Unlock()
conn.Write([]byte(OK))
conn.Flush()
}
func init() {

View File

@@ -3,6 +3,8 @@ package main
import "bufio"
type Server interface {
Lock()
Unlock()
GetData(key string) interface{}
SetData(key string, value interface{})
}

View File

@@ -10,6 +10,8 @@ import (
)
type Server interface {
Lock()
Unlock()
GetData(key string) interface{}
SetData(key string, value interface{})
}
@@ -53,11 +55,13 @@ func handleGet(cmd []string, s Server, conn *bufio.Writer) {
return
}
s.Lock()
value := s.GetData(cmd[1])
s.Unlock()
switch value.(type) {
default:
fmt.Println("Error. The requested object's type cannot be returned with the GET command")
conn.Write([]byte("-Error type cannot be returned with the GET command\r\n\n"))
case nil:
conn.Write([]byte("+nil\r\n\n"))
case string:
@@ -74,6 +78,8 @@ func handleGet(cmd []string, s Server, conn *bufio.Writer) {
func handleMGet(cmd []string, s Server, conn *bufio.Writer) {
vals := []string{}
s.Lock()
for _, key := range cmd[1:] {
switch s.GetData(key).(type) {
case nil:
@@ -87,6 +93,8 @@ func handleMGet(cmd []string, s Server, conn *bufio.Writer) {
}
}
s.Unlock()
conn.Write([]byte(fmt.Sprintf("*%d\r\n", len(vals))))
for _, val := range vals {
@@ -103,11 +111,15 @@ func handleSet(cmd []string, s Server, conn *bufio.Writer) {
conn.Write([]byte("-Error wrong number of args for SET command\r\n\n"))
conn.Flush()
case x > 3:
s.Lock()
s.SetData(cmd[1], strings.Join(cmd[2:], " "))
s.Unlock()
conn.Write([]byte("+OK\r\n"))
case x == 3:
val, err := strconv.ParseFloat(cmd[2], 32)
s.Lock()
if err != nil {
s.SetData(cmd[1], cmd[2])
} else if !utils.IsInteger(val) {
@@ -116,6 +128,8 @@ func handleSet(cmd []string, s Server, conn *bufio.Writer) {
s.SetData(cmd[1], int(val))
}
s.Unlock()
conn.Write([]byte("+OK\r\n\n"))
conn.Flush()
}

View File

@@ -9,7 +9,7 @@ import (
"math"
"os"
"path"
"sync"
"strconv"
"gopkg.in/yaml.v3"
)
@@ -96,6 +96,21 @@ func IsInteger(n float64) bool {
return math.Mod(n, 1.0) == 0
}
func AdaptType(s string) interface{} {
// Adapt the type of the parameter to string, float64 or int
n, err := strconv.ParseFloat(s, 32)
if err != nil {
return s
}
if IsInteger(n) {
return int(n)
}
return n
}
func ReadMessage(r *bufio.ReadWriter) (message string, err error) {
var line [][]byte
@@ -116,20 +131,3 @@ func ReadMessage(r *bufio.ReadWriter) (message string, err error) {
return fmt.Sprintf("%s\r\n", string(bytes.Join(line, []byte("\r\n")))), nil
}
type Plugin interface {
Name() string
Commands() []string
Description() string
HandleCommand(
cmd []string,
GetData *func(key string) interface{},
SetData *func(key string, value interface{}),
conn *bufio.Writer,
)
}
type Data struct {
mu sync.Mutex
data map[string]interface{}
}