mirror of
https://github.com/nalgeon/redka.git
synced 2025-10-10 10:20:06 +08:00
impr: command - dbsize, ttl, type
This commit is contained in:
@@ -23,6 +23,8 @@ func Parse(args [][]byte) (redis.Cmd, error) {
|
|||||||
// server
|
// server
|
||||||
case "command":
|
case "command":
|
||||||
return server.ParseOK(b)
|
return server.ParseOK(b)
|
||||||
|
case "dbsize":
|
||||||
|
return server.ParseDBSize(b)
|
||||||
case "flushdb":
|
case "flushdb":
|
||||||
return key.ParseFlushDB(b)
|
return key.ParseFlushDB(b)
|
||||||
case "info":
|
case "info":
|
||||||
@@ -59,6 +61,10 @@ func Parse(args [][]byte) (redis.Cmd, error) {
|
|||||||
return key.ParseRenameNX(b)
|
return key.ParseRenameNX(b)
|
||||||
case "scan":
|
case "scan":
|
||||||
return key.ParseScan(b)
|
return key.ParseScan(b)
|
||||||
|
case "ttl":
|
||||||
|
return key.ParseTTL(b)
|
||||||
|
case "type":
|
||||||
|
return key.ParseType(b)
|
||||||
|
|
||||||
// list
|
// list
|
||||||
case "lindex":
|
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
|
ExpireAt(key string, at time.Time) error
|
||||||
Get(key string) (core.Key, error)
|
Get(key string) (core.Key, error)
|
||||||
Keys(pattern string) ([]core.Key, error)
|
Keys(pattern string) ([]core.Key, error)
|
||||||
|
Len() (int, error)
|
||||||
Persist(key string) error
|
Persist(key string) error
|
||||||
Random() (core.Key, error)
|
Random() (core.Key, error)
|
||||||
Rename(key, newKey string) error
|
Rename(key, newKey string) error
|
||||||
|
Reference in New Issue
Block a user