mirror of
https://github.com/nalgeon/redka.git
synced 2025-10-11 02:40:14 +08:00
impr: command - dbsize, ttl, type
This commit is contained in:
@@ -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":
|
||||
@@ -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":
|
||||
|
44
internal/command/key/ttl.go
Normal file
44
internal/command/key/ttl.go
Normal file
@@ -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
internal/command/key/ttl_test.go
Normal file
85
internal/command/key/ttl_test.go
Normal file
@@ -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
internal/command/key/type.go
Normal file
37
internal/command/key/type.go
Normal file
@@ -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
internal/command/key/type_test.go
Normal file
74
internal/command/key/type_test.go
Normal file
@@ -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
internal/command/server/dbsize.go
Normal file
28
internal/command/server/dbsize.go
Normal file
@@ -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
internal/command/server/dbsize_test.go
Normal file
60
internal/command/server/dbsize_test.go
Normal file
@@ -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
internal/command/server/server_test.go
Normal file
17
internal/command/server/server_test.go
Normal file
@@ -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)
|
||||
}
|
@@ -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
|
||||
|
Reference in New Issue
Block a user