From b496d1086932cd0ab4bcbc5b302535ff033d6f40 Mon Sep 17 00:00:00 2001 From: xgzlucario <912156837@qq.com> Date: Sun, 9 Jun 2024 16:33:43 +0800 Subject: [PATCH] add: add cmd lpop, rpop --- Dockerfile | 4 +- README.md | 4 +- handler.go => command.go | 103 +++++++++++++++++++++++------ handler_test.go => command_test.go | 10 ++- errors.go | 4 ++ main.go | 6 +- rotom.go | 38 +---------- 7 files changed, 103 insertions(+), 66 deletions(-) rename handler.go => command.go (62%) rename handler_test.go => command_test.go (93%) diff --git a/Dockerfile b/Dockerfile index f4a388f..0e4277b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ WORKDIR /build COPY . . -RUN go build -o rotom . +RUN go build -ldflags="-s -w" -o rotom . FROM alpine:latest @@ -27,6 +27,6 @@ WORKDIR /data COPY --from=builder /build/rotom /data/rotom COPY config.json /etc/rotom/config.json -EXPOSE 6969 +EXPOSE 6379 CMD ["./rotom", "-config", "/etc/rotom/config.json"] \ No newline at end of file diff --git a/README.md b/README.md index 1d7eb48..52883ac 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ rotom 基于 [godis](https://github.com/archeryue/godis) 项目 2. 兼容 Redis RESP 协议,你可以使用任何 redis 客户端连接 rotom 3. DB hashmap 基于 [GigaCache](https://github.com/xgzlucario/GigaCache) 4. AOF 支持 -5. 目前仅支持部分命令如 `ping`, `set`, `get`, `hset`, `hget` +5. 目前支持命令 `set`, `get`, `hset`, `hget`, `hdel`, `lpush`, `rpush`, `lpop`, `rpop`, `lrange` ### 原理介绍 @@ -68,7 +68,7 @@ $ go run . ``` REPOSITORY TAG IMAGE ID CREATED SIZE -rotom latest 270888260e99 3 minutes ago 21.2MB +rotom latest 22f42ce9ae0e 8 seconds ago 18.4MB ``` 然后启动容器: diff --git a/handler.go b/command.go similarity index 62% rename from handler.go rename to command.go index 16509b8..269d00b 100644 --- a/handler.go +++ b/command.go @@ -1,26 +1,59 @@ package main import ( - "fmt" + "strings" "github.com/xgzlucario/rotom/structx" ) -var ( - // cmdTable is the list of all available commands. - cmdTable []*Command = []*Command{ - {"ping", pingCommand, 0, false}, - {"set", setCommand, 2, true}, - {"get", getCommand, 1, false}, - {"hset", hsetCommand, 3, true}, - {"hget", hgetCommand, 2, false}, - {"hdel", hdelCommand, 2, true}, - {"hgetall", hgetallCommand, 1, false}, - {"rpush", rpushCommand, 2, true}, - {"lpush", lpushCommand, 2, true}, - {"lrange", lrangeCommand, 3, false}, +type Command struct { + // name is command string name. + // it should consist of all lowercase letters. + name string + + // handler is this command real database handler function. + handler func([]Value) Value + + // arity represents the minimal number of arguments that command accepts. + arity int + + // persist indicates whether this command needs to be persisted. + // effective when `appendonly` is true. + persist bool +} + +// cmdTable is the list of all available commands. +var cmdTable []*Command = []*Command{ + {"ping", pingCommand, 0, false}, + {"set", setCommand, 2, true}, + {"get", getCommand, 1, false}, + {"hset", hsetCommand, 3, true}, + {"hget", hgetCommand, 2, false}, + {"hdel", hdelCommand, 2, true}, + {"hgetall", hgetallCommand, 1, false}, + {"rpush", rpushCommand, 2, true}, + {"lpush", lpushCommand, 2, true}, + {"rpop", rpopCommand, 1, true}, + {"lpop", lpopCommand, 1, true}, + {"lrange", lrangeCommand, 3, false}, +} + +func lookupCommand(command string) *Command { + cmdStr := strings.ToLower(command) + for _, c := range cmdTable { + if c.name == cmdStr { + return c + } } -) + return nil +} + +func (cmd *Command) processCommand(args []Value) Value { + if len(args) < cmd.arity { + return newErrValue(ErrWrongNumberArgs(cmd.name)) + } + return cmd.handler(args) +} func pingCommand(_ []Value) Value { return Value{typ: STRING, raw: []byte("PONG")} @@ -121,6 +154,14 @@ func hgetallCommand(args []Value) Value { return newArrayValue(res) } +func lpushCommand(args []Value) Value { + return pushInternal(args, true) +} + +func rpushCommand(args []Value) Value { + return pushInternal(args, false) +} + func pushInternal(args []Value, isDirectLeft bool) Value { key := args[0].ToString() @@ -140,12 +181,33 @@ func pushInternal(args []Value, isDirectLeft bool) Value { return newIntegerValue(ls.Size()) } -func lpushCommand(args []Value) Value { - return pushInternal(args, true) +func lpopCommand(args []Value) Value { + return popInternal(args, true) } -func rpushCommand(args []Value) Value { - return pushInternal(args, false) +func rpopCommand(args []Value) Value { + return popInternal(args, false) +} + +func popInternal(args []Value, isDirectLeft bool) Value { + key := args[0].ToString() + + ls, err := fetchList(key) + if err != nil { + return newErrValue(err) + } + + var val string + var ok bool + if isDirectLeft { + val, ok = ls.LPop() + } else { + val, ok = ls.RPop() + } + if ok { + return newBulkValue([]byte(val)) + } + return newBulkValue(nil) } func lrangeCommand(args []Value) Value { @@ -187,9 +249,8 @@ func fetch[T any](key string, new func() T, setnx ...bool) (v T, err error) { if ok { return v, nil } - return v, fmt.Errorf("wrong type assert: %T->%T", item, v) + return v, ErrWrongType } - v = new() if len(setnx) > 0 && setnx[0] { db.extras[key] = v diff --git a/handler_test.go b/command_test.go similarity index 93% rename from handler_test.go rename to command_test.go index 364a54d..2f6448a 100644 --- a/handler_test.go +++ b/command_test.go @@ -24,7 +24,7 @@ func startup() { var ctx = context.Background() -func TestHandler(t *testing.T) { +func TestCommand(t *testing.T) { t.Parallel() assert := assert.New(t) @@ -107,5 +107,13 @@ func TestHandler(t *testing.T) { // lrange res, _ := rdb.LRange(ctx, "list", 0, -1).Result() assert.Equal(res, []string{"c", "b", "a", "d", "e", "f"}) + + // lpop + val, _ := rdb.LPop(ctx, "list").Result() + assert.Equal(val, "c") + + // rpop + val, _ = rdb.RPop(ctx, "list").Result() + assert.Equal(val, "f") }) } diff --git a/errors.go b/errors.go index a029969..b4e569d 100644 --- a/errors.go +++ b/errors.go @@ -18,3 +18,7 @@ var ( func ErrWrongNumberArgs(cmd string) error { return fmt.Errorf("ERR wrong number of arguments for '%s' command", cmd) } + +func ErrUnknownCommand(cmd string) error { + return fmt.Errorf("ERR unknown command '%s'", cmd) +} diff --git a/main.go b/main.go index 9f7384b..fe06649 100644 --- a/main.go +++ b/main.go @@ -33,13 +33,13 @@ func main() { config, err := LoadConfig(path) if err != nil { - logger.Error().Msgf("load config error: %v", err) + logger.Fatal().Msgf("load config error: %v", err) } if err = initServer(config); err != nil { - logger.Error().Msgf("init server error: %v", err) + logger.Fatal().Msgf("init server error: %v", err) } if err = InitDB(config); err != nil { - logger.Error().Msgf("init db error: %v", err) + logger.Fatal().Msgf("init db error: %v", err) } if debug { runDebug() diff --git a/rotom.go b/rotom.go index 94d9755..c1b5651 100644 --- a/rotom.go +++ b/rotom.go @@ -1,9 +1,6 @@ package main import ( - "fmt" - "strings" - cache "github.com/xgzlucario/GigaCache" "github.com/xgzlucario/rotom/structx" ) @@ -41,44 +38,11 @@ type Server struct { clients map[int]*Client } -type Command struct { - // name is command string name. - // it should consist of all lowercase letters. - name string - - // handler is this command real database handler function. - handler func([]Value) Value - - // arity represents the minimal number of arguments that command accepts. - arity int - - // persist indicates whether this command needs to be persisted. - // effective when `appendonly` is true. - persist bool -} - var ( db DB server Server ) -func lookupCommand(command string) *Command { - cmdStr := strings.ToLower(command) - for _, c := range cmdTable { - if c.name == cmdStr { - return c - } - } - return nil -} - -func (cmd *Command) processCommand(args []Value) Value { - if len(args) < cmd.arity { - return newErrValue(ErrWrongNumberArgs(cmd.name)) - } - return cmd.handler(args) -} - // InitDB initializes database and redo appendonly files if nedded. func InitDB(config *Config) (err error) { options := cache.DefaultOptions @@ -181,7 +145,7 @@ func ProcessQueryBuf(client *Client) { db.aof.Write(queryBuf) } } else { - res = newErrValue(fmt.Errorf("invalid command: %s", command)) + res = newErrValue(ErrUnknownCommand(command)) } client.reply = append(client.reply, res)