aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralex <[email protected]>2026-07-06 13:35:07 +0200
committeralex <[email protected]>2026-07-06 13:35:07 +0200
commitf00398f4be24b5c3cce52c36dc71a42d1d0ccfbe (patch)
tree1459c3502db09b664d3ceac114b2c86affd09d11
parent8c49e03997128de4761acaaa6606d58f87d92c6c (diff)
downloadredis-clone-f00398f4be24b5c3cce52c36dc71a42d1d0ccfbe.tar.xz
redis-clone-f00398f4be24b5c3cce52c36dc71a42d1d0ccfbe.zip
split the code into multiple files for better readability
-rw-r--r--handlers.go269
-rw-r--r--main.go353
-rw-r--r--parser.go63
-rw-r--r--redisDB.go41
4 files changed, 373 insertions, 353 deletions
diff --git a/handlers.go b/handlers.go
new file mode 100644
index 0000000..4625f6a
--- /dev/null
+++ b/handlers.go
@@ -0,0 +1,269 @@
+package main
+
+import (
+ "errors"
+ "net"
+ "strconv"
+)
+
+func handleGet(conn net.Conn, key string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if !exists {
+ conn.Write(serializeRESP(nil))
+ return
+ }
+
+ strValue, ok := value.(string)
+ if !ok {
+ err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ conn.Write(serializeRESP(strValue))
+}
+
+func handleExists(conn net.Conn, key string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ _, exists := db.data[key]
+ if exists {
+ conn.Write(serializeRESP(1))
+ } else {
+ conn.Write(serializeRESP(0))
+ }
+}
+
+func handleSet(conn net.Conn, key, value string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ db.data[key] = value
+ conn.Write(serializeRESP(SimpleString("OK")))
+}
+
+func handleDel(conn net.Conn, key string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ _, exists := db.data[key]
+ if exists {
+ delete(db.data, key)
+ conn.Write(serializeRESP(1))
+ } else {
+ conn.Write(serializeRESP(0))
+ }
+}
+
+func handleIncr(conn net.Conn, key string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if exists {
+ strValue, ok := value.(string)
+ if !ok {
+ err := errors.New("value is not an integer or out of range")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ currentInt, err := strconv.Atoi(strValue)
+ if err != nil {
+ err := errors.New("value is not an integer or out of range")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ newValueStr := strconv.Itoa(currentInt + 1)
+ db.data[key] = newValueStr
+ conn.Write(serializeRESP(currentInt + 1))
+ } else {
+ db.data[key] = "1"
+ conn.Write(serializeRESP(1))
+ }
+}
+
+func handleDecr(conn net.Conn, key string) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if exists {
+ strValue, ok := value.(string)
+ if !ok {
+ err := errors.New("value is not an integer or out of range")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ currentInt, err := strconv.Atoi(strValue)
+ if err != nil {
+ err := errors.New("value is not an integer or out of range")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ newValueStr := strconv.Itoa(currentInt - 1)
+ db.data[key] = newValueStr
+ conn.Write(serializeRESP(currentInt - 1))
+ } else {
+ db.data[key] = "-1"
+ conn.Write(serializeRESP(-1))
+ }
+}
+
+func handlePing(conn net.Conn) {
+ conn.Write(serializeRESP(SimpleString("PONG")))
+}
+
+func handleFlushall(conn net.Conn) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ db.data = make(map[string]any)
+ conn.Write(serializeRESP(SimpleString("OK")))
+}
+
+func handleRpush(conn net.Conn, args []string) {
+ key := args[1]
+ newItems := args[2:]
+
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ var list []string
+ existingValue, exists := db.data[key]
+
+ if exists {
+ var ok bool
+ list, ok = existingValue.([]string)
+ if !ok {
+ err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
+ conn.Write(serializeRESP(err))
+ return
+ }
+ } else {
+ list = []string{}
+ }
+ list = append(list, newItems...)
+ db.data[key] = list
+ conn.Write(serializeRESP(len(list)))
+}
+
+func handleLrange(conn net.Conn, key string, start, stop int) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if !exists {
+ conn.Write(serializeRESP([]string{}))
+
+ return
+ }
+
+ typedList, ok := value.([]string)
+ if !ok {
+ err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ size := len(typedList)
+
+ if start < 0 {
+ start += size
+ if start < 0 {
+ start = 0
+ }
+ }
+
+ if stop < 0 {
+ stop += size
+ if stop < 0 {
+ stop = 0
+ }
+ }
+
+ if stop >= size {
+ stop = size - 1
+ }
+
+ if start > stop || start >= size {
+ conn.Write(serializeRESP([]string{}))
+ return
+ }
+
+ resultList := typedList[start : stop+1]
+ conn.Write(serializeRESP(resultList))
+}
+
+func handleLpop(conn net.Conn, key string){
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if !exists {
+ conn.Write(serializeRESP(nil))
+ return
+ }
+
+ typedList, ok := value.([]string)
+ if !ok {
+ err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ if len(typedList) == 0 {
+ conn.Write(serializeRESP(nil))
+ return
+ }
+
+ item := typedList[0]
+ newList := typedList[1:]
+ if len(newList) <= 0 {
+ delete(db.data, key)
+ }else{
+ db.data[key] = newList
+ }
+ conn.Write(serializeRESP(item))
+
+}
+
+func handleRpop(conn net.Conn, key string){
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ value, exists := db.data[key]
+ if !exists {
+ conn.Write(serializeRESP(nil))
+ return
+ }
+
+ typedList, ok := value.([]string)
+ if !ok {
+ err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
+ conn.Write(serializeRESP(err))
+ return
+ }
+
+ if len(typedList) == 0 {
+ conn.Write(serializeRESP(nil))
+ return
+ }
+
+ item := typedList[len(typedList)-1]
+ newList := typedList[:len(typedList)-1]
+ if len(newList) <= 0 {
+ delete(db.data, key)
+ }else{
+ db.data[key] = newList
+ }
+ conn.Write(serializeRESP(item))
+
+}
diff --git a/main.go b/main.go
index 2fd1bd3..81ff1fc 100644
--- a/main.go
+++ b/main.go
@@ -3,21 +3,12 @@ package main
import (
"bufio"
"fmt"
- "io"
"net"
"os"
"strconv"
"strings"
- "sync"
- "errors"
)
-type RedisDB struct {
- mu sync.Mutex
- data map[string]any
-}
-
-type SimpleString string
var db RedisDB
@@ -42,350 +33,6 @@ func main() {
}
}
-func handleGet(conn net.Conn, key string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if !exists {
- conn.Write(serializeRESP(nil))
- return
- }
-
- strValue, ok := value.(string)
- if !ok {
- err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
- conn.Write(serializeRESP(err))
- return
- }
-
- conn.Write(serializeRESP(strValue))
-}
-
-func handleExists(conn net.Conn, key string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- _, exists := db.data[key]
- if exists {
- conn.Write(serializeRESP(1))
- } else {
- conn.Write(serializeRESP(0))
- }
-}
-
-func handleSet(conn net.Conn, key, value string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- db.data[key] = value
- conn.Write(serializeRESP(SimpleString("OK")))
-}
-
-func handleDel(conn net.Conn, key string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- _, exists := db.data[key]
- if exists {
- delete(db.data, key)
- conn.Write(serializeRESP(1))
- } else {
- conn.Write(serializeRESP(0))
- }
-}
-
-func handleIncr(conn net.Conn, key string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if exists {
- strValue, ok := value.(string)
- if !ok {
- err := errors.New("value is not an integer or out of range")
- conn.Write(serializeRESP(err))
- return
- }
-
- currentInt, err := strconv.Atoi(strValue)
- if err != nil {
- err := errors.New("value is not an integer or out of range")
- conn.Write(serializeRESP(err))
- return
- }
-
- newValueStr := strconv.Itoa(currentInt + 1)
- db.data[key] = newValueStr
- conn.Write(serializeRESP(currentInt + 1))
- } else {
- db.data[key] = "1"
- conn.Write(serializeRESP(1))
- }
-}
-
-func handleDecr(conn net.Conn, key string) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if exists {
- strValue, ok := value.(string)
- if !ok {
- err := errors.New("value is not an integer or out of range")
- conn.Write(serializeRESP(err))
- return
- }
-
- currentInt, err := strconv.Atoi(strValue)
- if err != nil {
- err := errors.New("value is not an integer or out of range")
- conn.Write(serializeRESP(err))
- return
- }
-
- newValueStr := strconv.Itoa(currentInt - 1)
- db.data[key] = newValueStr
- conn.Write(serializeRESP(currentInt - 1))
- } else {
- db.data[key] = "-1"
- conn.Write(serializeRESP(-1))
- }
-}
-
-func handlePing(conn net.Conn) {
- conn.Write(serializeRESP(SimpleString("PONG")))
-}
-
-func handleFlushall(conn net.Conn) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- db.data = make(map[string]any)
- conn.Write(serializeRESP(SimpleString("OK")))
-}
-
-func handleRpush(conn net.Conn, args []string) {
- key := args[1]
- newItems := args[2:]
-
- db.mu.Lock()
- defer db.mu.Unlock()
-
- var list []string
- existingValue, exists := db.data[key]
-
- if exists {
- var ok bool
- list, ok = existingValue.([]string)
- if !ok {
- err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
- conn.Write(serializeRESP(err))
- return
- }
- } else {
- list = []string{}
- }
- list = append(list, newItems...)
- db.data[key] = list
- conn.Write(serializeRESP(len(list)))
-}
-
-func handleLrange(conn net.Conn, key string, start, stop int) {
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if !exists {
- conn.Write(serializeRESP([]string{}))
-
- return
- }
-
- typedList, ok := value.([]string)
- if !ok {
- err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
- conn.Write(serializeRESP(err))
- return
- }
-
- size := len(typedList)
-
- if start < 0 {
- start += size
- if start < 0 {
- start = 0
- }
- }
-
- if stop < 0 {
- stop += size
- if stop < 0 {
- stop = 0
- }
- }
-
- if stop >= size {
- stop = size - 1
- }
-
- if start > stop || start >= size {
- conn.Write(serializeRESP([]string{}))
- return
- }
-
- resultList := typedList[start : stop+1]
- conn.Write(serializeRESP(resultList))
-}
-
-func handleLpop(conn net.Conn, key string){
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if !exists {
- conn.Write(serializeRESP(nil))
- return
- }
-
- typedList, ok := value.([]string)
- if !ok {
- err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
- conn.Write(serializeRESP(err))
- return
- }
-
- if len(typedList) == 0 {
- conn.Write(serializeRESP(nil))
- return
- }
-
- item := typedList[0]
- newList := typedList[1:]
- if len(newList) <= 0 {
- delete(db.data, key)
- }else{
- db.data[key] = newList
- }
- conn.Write(serializeRESP(item))
-
-}
-
-func handleRpop(conn net.Conn, key string){
- db.mu.Lock()
- defer db.mu.Unlock()
-
- value, exists := db.data[key]
- if !exists {
- conn.Write(serializeRESP(nil))
- return
- }
-
- typedList, ok := value.([]string)
- if !ok {
- err := errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")
- conn.Write(serializeRESP(err))
- return
- }
-
- if len(typedList) == 0 {
- conn.Write(serializeRESP(nil))
- return
- }
-
- item := typedList[len(typedList)-1]
- newList := typedList[:len(typedList)-1]
- if len(newList) <= 0 {
- delete(db.data, key)
- }else{
- db.data[key] = newList
- }
- conn.Write(serializeRESP(item))
-
-}
-
-func serializeRESP(v any) []byte {
- switch val := v.(type) {
- case string:
- bulkString := fmt.Sprintf("$%d\r\n%s\r\n", len(val), val)
- return []byte(bulkString)
- case int:
- integerString := fmt.Sprintf(":%d\r\n",val)
- return []byte(integerString)
- case nil:
- return []byte("$-1\r\n")
- case error:
- errorString := fmt.Sprintf("-ERR %s\r\n",val.Error())
- return []byte(errorString)
- case []string:
- size := len(val)
- result := []byte(fmt.Sprintf("*%d\r\n", size))
- for i := 0; i < size; i++ {
- result = append(result, serializeRESP(val[i])...)
- }
- return result
- case SimpleString:
- simpleStr := fmt.Sprintf("+%s\r\n", val)
- return []byte(simpleStr)
- default:
- return []byte("-ERR internal server error: unknown type\r\n")
- }
-}
-
-func parseRESP(reader *bufio.Reader) ([]string, error) {
- line, err := reader.ReadString('\n')
- if err != nil {
- return nil, err
- }
-
- if len(line) == 0 {
- return nil, fmt.Errorf("empty line")
- }
-
- switch line[0] {
- case '*':
- var args []string
- argC, _ := strconv.Atoi(strings.TrimSpace(line[1:]))
-
- for i := 0; i < argC; i++ {
- nestedArgs, err := parseRESP(reader)
- if err != nil {
- return nil, err
- }
-
- args = append(args, nestedArgs...)
- }
- return args, nil
- case '$':
- strLen, _ := strconv.Atoi(strings.TrimSpace(line[1:]))
-
- if strLen == -1 {
- return []string{""}, nil
- }
-
- buf := make([]byte, strLen)
-
- _, err = io.ReadFull(reader, buf)
- if err != nil {
- return nil, err
- }
-
- _, err = reader.Discard(2)
- if err != nil {
- return nil, err
- }
-
- return []string{string(buf)}, nil
- case '+':
- cleanStr := strings.TrimSpace(line[1:])
- return []string{cleanStr}, nil
- case ':':
- return []string{strings.TrimSpace(line[1:])}, nil
- default:
- return nil, fmt.Errorf("unknown command type: %c", line[0])
- }
-}
-
func handleConnection(conn net.Conn) {
defer conn.Close()
fmt.Println("Client connected:", conn.RemoteAddr())
diff --git a/parser.go b/parser.go
new file mode 100644
index 0000000..054e906
--- /dev/null
+++ b/parser.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+)
+
+func parseRESP(reader *bufio.Reader) ([]string, error) {
+ line, err := reader.ReadString('\n')
+ if err != nil {
+ return nil, err
+ }
+
+ if len(line) == 0 {
+ return nil, fmt.Errorf("empty line")
+ }
+
+ switch line[0] {
+ case '*':
+ var args []string
+ argC, _ := strconv.Atoi(strings.TrimSpace(line[1:]))
+
+ for i := 0; i < argC; i++ {
+ nestedArgs, err := parseRESP(reader)
+ if err != nil {
+ return nil, err
+ }
+
+ args = append(args, nestedArgs...)
+ }
+ return args, nil
+ case '$':
+ strLen, _ := strconv.Atoi(strings.TrimSpace(line[1:]))
+
+ if strLen == -1 {
+ return []string{""}, nil
+ }
+
+ buf := make([]byte, strLen)
+
+ _, err = io.ReadFull(reader, buf)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = reader.Discard(2)
+ if err != nil {
+ return nil, err
+ }
+
+ return []string{string(buf)}, nil
+ case '+':
+ cleanStr := strings.TrimSpace(line[1:])
+ return []string{cleanStr}, nil
+ case ':':
+ return []string{strings.TrimSpace(line[1:])}, nil
+ default:
+ return nil, fmt.Errorf("unknown command type: %c", line[0])
+ }
+} \ No newline at end of file
diff --git a/redisDB.go b/redisDB.go
new file mode 100644
index 0000000..f21ac50
--- /dev/null
+++ b/redisDB.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+type RedisDB struct {
+ mu sync.Mutex
+ data map[string]any
+}
+
+type SimpleString string
+
+func serializeRESP(v any) []byte {
+ switch val := v.(type) {
+ case string:
+ bulkString := fmt.Sprintf("$%d\r\n%s\r\n", len(val), val)
+ return []byte(bulkString)
+ case int:
+ integerString := fmt.Sprintf(":%d\r\n",val)
+ return []byte(integerString)
+ case nil:
+ return []byte("$-1\r\n")
+ case error:
+ errorString := fmt.Sprintf("-ERR %s\r\n",val.Error())
+ return []byte(errorString)
+ case []string:
+ size := len(val)
+ result := []byte(fmt.Sprintf("*%d\r\n", size))
+ for i := 0; i < size; i++ {
+ result = append(result, serializeRESP(val[i])...)
+ }
+ return result
+ case SimpleString:
+ simpleStr := fmt.Sprintf("+%s\r\n", val)
+ return []byte(simpleStr)
+ default:
+ return []byte("-ERR internal server error: unknown type\r\n")
+ }
+}