Skip to content

Commit

Permalink
add: add cmd lpop, rpop
Browse files Browse the repository at this point in the history
  • Loading branch information
xgzlucario committed Jun 9, 2024
1 parent 14efecf commit b496d10
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 66 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ WORKDIR /build

COPY . .

RUN go build -o rotom .
RUN go build -ldflags="-s -w" -o rotom .

FROM alpine:latest

Expand All @@ -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"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

### 原理介绍

Expand Down Expand Up @@ -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
```

然后启动容器:
Expand Down
103 changes: 82 additions & 21 deletions handler.go → command.go
Original file line number Diff line number Diff line change
@@ -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")}
Expand Down Expand Up @@ -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()

Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion handler_test.go → command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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")
})
}
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 3 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
38 changes: 1 addition & 37 deletions rotom.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package main

import (
"fmt"
"strings"

cache "github.com/xgzlucario/GigaCache"
"github.com/xgzlucario/rotom/structx"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit b496d10

Please sign in to comment.