mirror of
https://github.com/EchoVault/SugarDB.git
synced 2025-10-12 11:20:08 +08:00
Added Error to decoded types.
Removed cmd_functions.go as it is replaced by plugin system. Consolidated get and set plugins into one plugin setget that handles GET, SET and MGET commands. Plugins can declare multiple commands that they handle.
This commit is contained in:
@@ -17,7 +17,7 @@ func Decode(raw string) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.Contains[string]([]string{"SimpleString", "Integer"}, v.Type().String()) {
|
if utils.Contains[string]([]string{"SimpleString", "Integer", "Error"}, v.Type().String()) {
|
||||||
return []string{v.String()}, nil
|
return []string{v.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
build-plugins:
|
build-plugins:
|
||||||
go build -buildmode=plugin -o bin/plugins/commands/command_get.so plugins/commands/get/get.go
|
go build -buildmode=plugin -o bin/plugins/commands/command_setget.so plugins/commands/setget/setget.go
|
||||||
go build -buildmode=plugin -o bin/plugins/commands/command_set.so plugins/commands/set/set.go
|
|
||||||
|
|
||||||
build-server:
|
build-server:
|
||||||
go build -o bin/server ./*.go
|
go build -o bin/server ./*.go
|
||||||
|
@@ -1,116 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/kelvinmwinuka/memstore/serialization"
|
|
||||||
"github.com/kelvinmwinuka/memstore/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func processPing(cmd []string, connRW *bufio.ReadWriter) {
|
|
||||||
if len(cmd) == 1 {
|
|
||||||
serialization.Encode(connRW, "SimpleString PONG")
|
|
||||||
connRW.Write([]byte("\n"))
|
|
||||||
connRW.Flush()
|
|
||||||
}
|
|
||||||
if len(cmd) == 2 {
|
|
||||||
serialization.Encode(connRW, fmt.Sprintf("SimpleString \"%s\"", cmd[1]))
|
|
||||||
connRW.Write([]byte("\n"))
|
|
||||||
connRW.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func processSet(cmd []string, connRW *bufio.ReadWriter, server *Server) {
|
|
||||||
fmt.Println("Process set command")
|
|
||||||
server.data.mu.Lock()
|
|
||||||
defer server.data.mu.Unlock()
|
|
||||||
|
|
||||||
switch x := len(cmd); {
|
|
||||||
default:
|
|
||||||
fmt.Println("Wrong number of args for SET commands")
|
|
||||||
case x > 3:
|
|
||||||
server.data.data[cmd[1]] = strings.Join(cmd[2:], " ")
|
|
||||||
serialization.Encode(connRW, "SimpleString OK")
|
|
||||||
case x == 3:
|
|
||||||
val, err := strconv.ParseFloat(cmd[2], 32)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
server.data.data[cmd[1]] = cmd[2]
|
|
||||||
} else if !utils.IsInteger(val) {
|
|
||||||
server.data.data[cmd[1]] = val
|
|
||||||
} else {
|
|
||||||
server.data.data[cmd[1]] = int(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
serialization.Encode(connRW, "SimpleString OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
connRW.Write([]byte("\n"))
|
|
||||||
connRW.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func processGet(cmd []string, connRW *bufio.ReadWriter, server *Server) {
|
|
||||||
server.data.mu.Lock()
|
|
||||||
defer server.data.mu.Unlock()
|
|
||||||
|
|
||||||
// Use reflection to determine the type of the value and how to encode it
|
|
||||||
switch server.data.data[cmd[1]].(type) {
|
|
||||||
default:
|
|
||||||
fmt.Println("Error. The requested object's type cannot be returned with the GET command")
|
|
||||||
case nil:
|
|
||||||
serialization.Encode(connRW, "SimpleString nil")
|
|
||||||
case string:
|
|
||||||
serialization.Encode(connRW, fmt.Sprintf("SimpleString \"%s\"", server.data.data[cmd[1]]))
|
|
||||||
case float64:
|
|
||||||
serialization.Encode(connRW, fmt.Sprintf("SimpleString %f", server.data.data[cmd[1]]))
|
|
||||||
case int:
|
|
||||||
serialization.Encode(connRW, fmt.Sprintf("Integer %d", server.data.data[cmd[1]]))
|
|
||||||
}
|
|
||||||
|
|
||||||
connRW.Write([]byte("\n"))
|
|
||||||
connRW.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func processMGet(cmd []string, connRW *bufio.ReadWriter, server *Server) {
|
|
||||||
server.data.mu.Lock()
|
|
||||||
defer server.data.mu.Unlock()
|
|
||||||
|
|
||||||
vals := []string{}
|
|
||||||
|
|
||||||
for _, key := range cmd[1:] {
|
|
||||||
switch server.data.data[key].(type) {
|
|
||||||
case nil:
|
|
||||||
vals = append(vals, "nil")
|
|
||||||
case string:
|
|
||||||
vals = append(vals, fmt.Sprintf("%s", server.data.data[key]))
|
|
||||||
case float64:
|
|
||||||
vals = append(vals, fmt.Sprintf("%f", server.data.data[key]))
|
|
||||||
case int:
|
|
||||||
vals = append(vals, fmt.Sprintf("%d", server.data.data[key]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serialization.Encode(connRW, fmt.Sprintf("Array %s", strings.Join(vals, " ")))
|
|
||||||
|
|
||||||
connRW.Write([]byte("\n"))
|
|
||||||
connRW.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func processCommand(cmd []string, connRW *bufio.ReadWriter, server *Server) {
|
|
||||||
// Return encoded message to client
|
|
||||||
switch strings.ToLower(cmd[0]) {
|
|
||||||
default:
|
|
||||||
fmt.Println("The command is unknown")
|
|
||||||
case "ping":
|
|
||||||
processPing(cmd, connRW)
|
|
||||||
case "set":
|
|
||||||
processSet(cmd, connRW, server)
|
|
||||||
case "get":
|
|
||||||
processGet(cmd, connRW, server)
|
|
||||||
case "mget":
|
|
||||||
processMGet(cmd, connRW, server)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,10 +5,10 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"plugin"
|
"plugin"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,9 +20,9 @@ import (
|
|||||||
|
|
||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
Command() string
|
Commands() []string
|
||||||
Description() string
|
Description() string
|
||||||
HandleCommand(tokens []string, server interface{}) error
|
HandleCommand(cmd []string, server interface{}, conn *bufio.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
@@ -36,6 +36,18 @@ type Server struct {
|
|||||||
plugins []Plugin
|
plugins []Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (server *Server) GetData(key string) interface{} {
|
||||||
|
server.data.mu.Lock()
|
||||||
|
defer server.data.mu.Unlock()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (server *Server) handleConnection(conn net.Conn) {
|
func (server *Server) handleConnection(conn net.Conn) {
|
||||||
connRW := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
connRW := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||||
|
|
||||||
@@ -57,7 +69,14 @@ func (server *Server) handleConnection(conn net.Conn) {
|
|||||||
serialization.Encode(connRW, fmt.Sprintf("Error %s", err.Error()))
|
serialization.Encode(connRW, fmt.Sprintf("Error %s", err.Error()))
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
processCommand(cmd, connRW, server)
|
// Look for plugin that handles this command and trigger it
|
||||||
|
for _, plugin := range server.plugins {
|
||||||
|
fmt.Println(server)
|
||||||
|
if utils.Contains[string](plugin.Commands(), strings.ToLower(cmd[0])) {
|
||||||
|
plugin.HandleCommand(cmd, server, connRW.Writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +152,7 @@ func (server *Server) LoadPlugins() {
|
|||||||
conf := server.config
|
conf := server.config
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
pluginDirs, err := ioutil.ReadDir(conf.Plugins)
|
pluginDirs, err := os.ReadDir(conf.Plugins)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -143,7 +162,7 @@ func (server *Server) LoadPlugins() {
|
|||||||
if file.IsDir() {
|
if file.IsDir() {
|
||||||
switch file.Name() {
|
switch file.Name() {
|
||||||
case "commands":
|
case "commands":
|
||||||
files, err := ioutil.ReadDir(path.Join(conf.Plugins, "commands"))
|
files, err := os.ReadDir(path.Join(conf.Plugins, "commands"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -173,8 +192,9 @@ func (server *Server) LoadPlugins() {
|
|||||||
|
|
||||||
// Check if a plugin that handles the same command already exists
|
// Check if a plugin that handles the same command already exists
|
||||||
for _, loadedPlugin := range server.plugins {
|
for _, loadedPlugin := range server.plugins {
|
||||||
if loadedPlugin.Command() == plugin.Command() {
|
containsMutual, elem := utils.ContainsMutual[string](loadedPlugin.Commands(), plugin.Commands())
|
||||||
fmt.Printf("plugin that handles %s command already exists. Please handle a different command.", plugin.Command())
|
if containsMutual {
|
||||||
|
fmt.Printf("plugin that handles %s command already exists. Please handle a different command.\n", elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +208,7 @@ func (server *Server) LoadPlugins() {
|
|||||||
func (server *Server) Start() {
|
func (server *Server) Start() {
|
||||||
server.data.data = make(map[string]interface{})
|
server.data.data = make(map[string]interface{})
|
||||||
|
|
||||||
|
server.config = utils.GetConfig()
|
||||||
conf := server.config
|
conf := server.config
|
||||||
|
|
||||||
server.LoadPlugins()
|
server.LoadPlugins()
|
||||||
@@ -205,10 +226,6 @@ func (server *Server) Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
server := Server{
|
server := Server{}
|
||||||
config: utils.GetConfig(),
|
|
||||||
plugins: []Plugin{},
|
|
||||||
}
|
|
||||||
|
|
||||||
server.Start()
|
server.Start()
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
type Server interface {
|
|
||||||
GetData(key string) interface{}
|
|
||||||
SetData(key string, value interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type plugin struct {
|
|
||||||
name string
|
|
||||||
command string
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
var Plugin plugin
|
|
||||||
|
|
||||||
func (p *plugin) Name() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) Command() string {
|
|
||||||
return p.command
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) Description() string {
|
|
||||||
return p.description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) HandleCommand(tokens []string, server interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Plugin.name = "GetCommand"
|
|
||||||
Plugin.command = "get"
|
|
||||||
Plugin.description = "Get the value from the specified key"
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
type Server interface {
|
|
||||||
GetData(key string) interface{}
|
|
||||||
SetData(key string, value interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type plugin struct {
|
|
||||||
name string
|
|
||||||
command string
|
|
||||||
description string
|
|
||||||
}
|
|
||||||
|
|
||||||
var Plugin plugin
|
|
||||||
|
|
||||||
func (p *plugin) Name() string {
|
|
||||||
return p.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) Command() string {
|
|
||||||
return p.command
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) Description() string {
|
|
||||||
return p.description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *plugin) HandleCommand(tokens []string, server interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Plugin.name = "SetCommand"
|
|
||||||
Plugin.command = "set"
|
|
||||||
Plugin.description = "Set the value of the specified key"
|
|
||||||
}
|
|
128
server/plugins/commands/setget/setget.go
Normal file
128
server/plugins/commands/setget/setget.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kelvinmwinuka/memstore/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server interface {
|
||||||
|
GetData(key string) interface{}
|
||||||
|
SetData(key string, value interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type plugin struct {
|
||||||
|
name string
|
||||||
|
commands []string
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
var Plugin plugin
|
||||||
|
|
||||||
|
func (p *plugin) Name() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) Commands() []string {
|
||||||
|
return p.commands
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) Description() string {
|
||||||
|
return p.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *plugin) HandleCommand(cmd []string, server interface{}, conn *bufio.Writer) {
|
||||||
|
switch strings.ToLower(cmd[0]) {
|
||||||
|
case "get":
|
||||||
|
handleGet(cmd, server.(Server), conn)
|
||||||
|
case "set":
|
||||||
|
handleSet(cmd, server.(Server), conn)
|
||||||
|
case "mget":
|
||||||
|
handleMGet(cmd, server.(Server), conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGet(cmd []string, s Server, conn *bufio.Writer) {
|
||||||
|
|
||||||
|
if len(cmd) != 2 {
|
||||||
|
conn.Write([]byte("-Error wrong number of args for GET command\r\n\n"))
|
||||||
|
conn.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
value := s.GetData(cmd[1])
|
||||||
|
|
||||||
|
switch value.(type) {
|
||||||
|
default:
|
||||||
|
fmt.Println("Error. The requested object's type cannot be returned with the GET command")
|
||||||
|
case nil:
|
||||||
|
conn.Write([]byte("+nil\r\n\n"))
|
||||||
|
case string:
|
||||||
|
conn.Write([]byte(fmt.Sprintf("+%s\r\n\n", value)))
|
||||||
|
case float64:
|
||||||
|
conn.Write([]byte(fmt.Sprintf("+%f\r\n\n", value)))
|
||||||
|
case int:
|
||||||
|
conn.Write([]byte(fmt.Sprintf("+%d\r\n\n", value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMGet(cmd []string, s Server, conn *bufio.Writer) {
|
||||||
|
vals := []string{}
|
||||||
|
|
||||||
|
for _, key := range cmd[1:] {
|
||||||
|
switch s.GetData(key).(type) {
|
||||||
|
case nil:
|
||||||
|
vals = append(vals, "nil")
|
||||||
|
case string:
|
||||||
|
vals = append(vals, fmt.Sprintf("%s", s.GetData(key)))
|
||||||
|
case float64:
|
||||||
|
vals = append(vals, fmt.Sprintf("%f", s.GetData(key)))
|
||||||
|
case int:
|
||||||
|
vals = append(vals, fmt.Sprintf("%d", s.GetData(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Write([]byte(fmt.Sprintf("*%d\r\n", len(vals))))
|
||||||
|
|
||||||
|
for _, val := range vals {
|
||||||
|
conn.Write([]byte(fmt.Sprintf("$%d\r\n%s\r\n", len(val), val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Write([]byte("\n"))
|
||||||
|
conn.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSet(cmd []string, s Server, conn *bufio.Writer) {
|
||||||
|
switch x := len(cmd); {
|
||||||
|
default:
|
||||||
|
conn.Write([]byte("-Error wrong number of args for SET command\r\n\n"))
|
||||||
|
conn.Flush()
|
||||||
|
case x > 3:
|
||||||
|
s.SetData(cmd[1], strings.Join(cmd[2:], " "))
|
||||||
|
conn.Write([]byte("+OK\r\n"))
|
||||||
|
case x == 3:
|
||||||
|
val, err := strconv.ParseFloat(cmd[2], 32)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.SetData(cmd[1], cmd[2])
|
||||||
|
} else if !utils.IsInteger(val) {
|
||||||
|
s.SetData(cmd[1], val)
|
||||||
|
} else {
|
||||||
|
s.SetData(cmd[1], int(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Write([]byte("+OK\r\n\n"))
|
||||||
|
conn.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Plugin.name = "GetCommand"
|
||||||
|
Plugin.commands = []string{"set", "get", "mget"}
|
||||||
|
Plugin.description = "Handle basic SET, GET and MGET commands"
|
||||||
|
}
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -80,6 +81,17 @@ func Contains[T comparable](arr []T, elem T) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ContainsMutual[T comparable](arr1 []T, arr2 []T) (bool, T) {
|
||||||
|
for _, a := range arr1 {
|
||||||
|
for _, b := range arr2 {
|
||||||
|
if a == b {
|
||||||
|
return true, a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, arr1[0]
|
||||||
|
}
|
||||||
|
|
||||||
func IsInteger(n float64) bool {
|
func IsInteger(n float64) bool {
|
||||||
return math.Mod(n, 1.0) == 0
|
return math.Mod(n, 1.0) == 0
|
||||||
}
|
}
|
||||||
@@ -104,3 +116,20 @@ func ReadMessage(r *bufio.ReadWriter) (message string, err error) {
|
|||||||
|
|
||||||
return fmt.Sprintf("%s\r\n", string(bytes.Join(line, []byte("\r\n")))), nil
|
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{}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user