From f00398f4be24b5c3cce52c36dc71a42d1d0ccfbe Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 6 Jul 2026 13:35:07 +0200 Subject: split the code into multiple files for better readability --- handlers.go | 269 +++++++++++++++++++++++++++++++++++++++++++++ main.go | 353 ------------------------------------------------------------ parser.go | 63 +++++++++++ redisDB.go | 41 +++++++ 4 files changed, 373 insertions(+), 353 deletions(-) create mode 100644 handlers.go create mode 100644 parser.go create mode 100644 redisDB.go 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") + } +} -- cgit v1.2.3