Skip to content

Commit

Permalink
impr: command - dbsize, ttl, type
Browse files Browse the repository at this point in the history
  • Loading branch information
nalgeon committed May 4, 2024
1 parent 1a54e32 commit 91ba6db
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 0 deletions.
6 changes: 6 additions & 0 deletions internal/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ func Parse(args [][]byte) (redis.Cmd, error) {
// server
case "command":
return server.ParseOK(b)
case "dbsize":
return server.ParseDBSize(b)
case "flushdb":
return key.ParseFlushDB(b)
case "info":
Expand Down Expand Up @@ -59,6 +61,10 @@ func Parse(args [][]byte) (redis.Cmd, error) {
return key.ParseRenameNX(b)
case "scan":
return key.ParseScan(b)
case "ttl":
return key.ParseTTL(b)
case "type":
return key.ParseType(b)

// list
case "lindex":
Expand Down
44 changes: 44 additions & 0 deletions internal/command/key/ttl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package key

import (
"time"

"github.com/nalgeon/redka/internal/core"
"github.com/nalgeon/redka/internal/redis"
)

// Returns the expiration time in seconds of a key.
// TTL key
// https://redis.io/commands/ttl
type TTL struct {
redis.BaseCmd
key string
}

func ParseTTL(b redis.BaseCmd) (*TTL, error) {
cmd := &TTL{BaseCmd: b}
if len(cmd.Args()) != 1 {
return cmd, redis.ErrInvalidArgNum
}
cmd.key = string(cmd.Args()[0])
return cmd, nil
}

func (cmd *TTL) Run(w redis.Writer, red redis.Redka) (any, error) {
k, err := red.Key().Get(cmd.key)
if err == core.ErrNotFound {
w.WriteInt(-2)
return -2, nil
}
if err != nil {
w.WriteError(cmd.Error(err))
return nil, err
}
if k.ETime == nil {
w.WriteInt(-1)
return -1, nil
}
ttl := int(*k.ETime/1000 - time.Now().Unix())
w.WriteInt(ttl)
return ttl, nil
}
85 changes: 85 additions & 0 deletions internal/command/key/ttl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package key

import (
"testing"
"time"

"github.com/nalgeon/redka/internal/redis"
"github.com/nalgeon/redka/internal/testx"
)

func TestTTLParse(t *testing.T) {
tests := []struct {
cmd string
key string
err error
}{
{
cmd: "ttl",
key: "",
err: redis.ErrInvalidArgNum,
},
{
cmd: "ttl name",
key: "name",
err: nil,
},
{
cmd: "ttl name age",
key: "",
err: redis.ErrInvalidArgNum,
},
}

for _, test := range tests {
t.Run(test.cmd, func(t *testing.T) {
cmd, err := redis.Parse(ParseTTL, test.cmd)
testx.AssertEqual(t, err, test.err)
if err == nil {
testx.AssertEqual(t, cmd.key, test.key)
}
})
}
}

func TestTTLExec(t *testing.T) {
t.Run("has ttl", func(t *testing.T) {
db, red := getDB(t)
defer db.Close()

_ = db.Str().SetExpires("name", "alice", 60*time.Second)

cmd := redis.MustParse(ParseTTL, "ttl name")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, 60)
testx.AssertEqual(t, conn.Out(), "60")
})

t.Run("no ttl", func(t *testing.T) {
db, red := getDB(t)
defer db.Close()

_ = db.Str().Set("name", "alice")

cmd := redis.MustParse(ParseTTL, "ttl name")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, -1)
testx.AssertEqual(t, conn.Out(), "-1")
})

t.Run("not found", func(t *testing.T) {
db, red := getDB(t)
defer db.Close()

cmd := redis.MustParse(ParseTTL, "ttl name")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, -2)
testx.AssertEqual(t, conn.Out(), "-2")
})
}
37 changes: 37 additions & 0 deletions internal/command/key/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package key

import (
"github.com/nalgeon/redka/internal/core"
"github.com/nalgeon/redka/internal/redis"
)

// Determines the type of value stored at a key.
// TYPE key
// https://redis.io/commands/type
type Type struct {
redis.BaseCmd
key string
}

func ParseType(b redis.BaseCmd) (*Type, error) {
cmd := &Type{BaseCmd: b}
if len(cmd.Args()) != 1 {
return cmd, redis.ErrInvalidArgNum
}
cmd.key = string(cmd.Args()[0])
return cmd, nil
}

func (cmd *Type) Run(w redis.Writer, red redis.Redka) (any, error) {
k, err := red.Key().Get(cmd.key)
if err == core.ErrNotFound {
w.WriteString("none")
return "none", nil
}
if err != nil {
w.WriteError(cmd.Error(err))
return nil, err
}
w.WriteString(k.TypeName())
return k.TypeName(), nil
}
74 changes: 74 additions & 0 deletions internal/command/key/type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package key

import (
"testing"

"github.com/nalgeon/redka/internal/redis"
"github.com/nalgeon/redka/internal/testx"
)

func TestTypeParse(t *testing.T) {
tests := []struct {
cmd string
key string
err error
}{
{
cmd: "type",
key: "",
err: redis.ErrInvalidArgNum,
},
{
cmd: "type name",
key: "name",
err: nil,
},
{
cmd: "type name age",
key: "",
err: redis.ErrInvalidArgNum,
},
}

for _, test := range tests {
t.Run(test.cmd, func(t *testing.T) {
cmd, err := redis.Parse(ParseType, test.cmd)
testx.AssertEqual(t, err, test.err)
if err == nil {
testx.AssertEqual(t, cmd.key, test.key)
}
})
}
}

func TestTypeExec(t *testing.T) {
db, red := getDB(t)
defer db.Close()

_ = db.Str().Set("kstr", "string")
_, _ = db.List().PushBack("klist", "list")
_, _ = db.Hash().Set("khash", "field", "hash")
_, _ = db.ZSet().Add("kzset", "zset", 1)

tests := []struct {
key string
want string
}{
{key: "kstr", want: "string"},
{key: "klist", want: "list"},
{key: "khash", want: "hash"},
{key: "kzset", want: "zset"},
{key: "knone", want: "none"},
}

for _, test := range tests {
t.Run(test.key, func(t *testing.T) {
cmd := redis.MustParse(ParseType, "type "+test.key)
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, test.want)
testx.AssertEqual(t, conn.Out(), test.want)
})
}
}
28 changes: 28 additions & 0 deletions internal/command/server/dbsize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package server

import "github.com/nalgeon/redka/internal/redis"

// Returns the number of keys in the database.
// DBSIZE
// https://redis.io/commands/dbsize
type DBSize struct {
redis.BaseCmd
}

func ParseDBSize(b redis.BaseCmd) (*DBSize, error) {
cmd := &DBSize{BaseCmd: b}
if len(cmd.Args()) != 0 {
return cmd, redis.ErrInvalidArgNum
}
return cmd, nil
}

func (cmd *DBSize) Run(w redis.Writer, red redis.Redka) (any, error) {
n, err := red.Key().Len()
if err != nil {
w.WriteError(cmd.Error(err))
return nil, err
}
w.WriteInt(n)
return n, nil
}
60 changes: 60 additions & 0 deletions internal/command/server/dbsize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package server

import (
"testing"

"github.com/nalgeon/redka/internal/redis"
"github.com/nalgeon/redka/internal/testx"
)

func TestDBSizeParse(t *testing.T) {
tests := []struct {
cmd string
err error
}{
{
cmd: "dbsize",
err: nil,
},
{
cmd: "dbsize name",
err: redis.ErrInvalidArgNum,
},
}

for _, test := range tests {
t.Run(test.cmd, func(t *testing.T) {
_, err := redis.Parse(ParseDBSize, test.cmd)
testx.AssertEqual(t, err, test.err)
})
}
}

func TestDBSizeExec(t *testing.T) {
t.Run("dbsize", func(t *testing.T) {
db, red := getDB(t)
defer db.Close()

_ = db.Str().Set("name", "alice")
_ = db.Str().Set("age", 25)

cmd := redis.MustParse(ParseDBSize, "dbsize")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, 2)
testx.AssertEqual(t, conn.Out(), "2")
})

t.Run("empty", func(t *testing.T) {
db, red := getDB(t)
defer db.Close()

cmd := redis.MustParse(ParseDBSize, "dbsize")
conn := redis.NewFakeConn()
res, err := cmd.Run(conn, red)
testx.AssertNoErr(t, err)
testx.AssertEqual(t, res, 0)
testx.AssertEqual(t, conn.Out(), "0")
})
}
17 changes: 17 additions & 0 deletions internal/command/server/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package server

import (
"testing"

"github.com/nalgeon/redka"
"github.com/nalgeon/redka/internal/redis"
)

func getDB(tb testing.TB) (*redka.DB, redis.Redka) {
tb.Helper()
db, err := redka.Open(":memory:", nil)
if err != nil {
tb.Fatal(err)
}
return db, redis.RedkaDB(db)
}
1 change: 1 addition & 0 deletions internal/redis/redka.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type RKey interface {
ExpireAt(key string, at time.Time) error
Get(key string) (core.Key, error)
Keys(pattern string) ([]core.Key, error)
Len() (int, error)
Persist(key string) error
Random() (core.Key, error)
Rename(key, newKey string) error
Expand Down

0 comments on commit 91ba6db

Please sign in to comment.