Skip to content

Commit

Permalink
Add PEXPIRETIME command
Browse files Browse the repository at this point in the history
Redis documentation: https://redis.io/commands/pexpiretime/

Signed-off-by: Wojciech Szarański <wojciech.szaranski@gmail.com>
  • Loading branch information
wszaranski authored and alicebob committed Mar 13, 2024
1 parent 1033c7e commit 3d911c1
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 32 deletions.
78 changes: 47 additions & 31 deletions cmd_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ import (
)

const (
// expiretimeReplyNoExpiration is returned by [Miniredis.cmdExpireTime] if the key exists but has no associated expiration time
// expiretimeReplyNoExpiration is return value for EXPIRETIME and PEXPIRETIME if the key exists but has no associated expiration time
expiretimeReplyNoExpiration = -1
// expiretimeReplyMissingKey is returned by [Miniredis.cmdExpireTime] if the key does not exist
// expiretimeReplyMissingKey is return value for EXPIRETIME and PEXPIRETIME if the key does not exist
expiretimeReplyMissingKey = -2
)

func inSeconds(t time.Time) int {
return int(t.Unix())
}

func inMilliSeconds(t time.Time) int {
// Time.UnixMilli() was added in go 1.17
// return int(t.UnixNano() / 1000000) is limited to dates between year 1678 and 2262
// by using following calculation we extend this time without too much complexity
return int(t.Unix())*1000 + t.Nanosecond()/1000000
}

// commandsGeneric handles EXPIRE, TTL, PERSIST, &c.
func commandsGeneric(m *Miniredis) {
m.srv.Register("COPY", m.cmdCopy)
Expand All @@ -27,7 +38,8 @@ func commandsGeneric(m *Miniredis) {
m.srv.Register("EXISTS", m.cmdExists)
m.srv.Register("EXPIRE", makeCmdExpire(m, false, time.Second))
m.srv.Register("EXPIREAT", makeCmdExpire(m, true, time.Second))
m.srv.Register("EXPIRETIME", m.cmdExpireTime)
m.srv.Register("EXPIRETIME", m.makeCmdExpireTime(inSeconds))
m.srv.Register("PEXPIRETIME", m.makeCmdExpireTime(inMilliSeconds))
m.srv.Register("KEYS", m.cmdKeys)
// MIGRATE
m.srv.Register("MOVE", m.cmdMove)
Expand Down Expand Up @@ -153,41 +165,45 @@ func makeCmdExpire(m *Miniredis, unix bool, d time.Duration) func(*server.Peer,
}
}

// cmdExpireTime returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.
// See [redis documentation].
// makeCmdExpireTime creates server command function that returns the absolute Unix timestamp (since January 1, 1970)
// at which the given key will expire, in unit selected by time result strategy (e.g. seconds, milliseconds).
// For more information see redis documentation for [expiretime] and [pexpiretime].
//
// [redis documentation]: https://redis.io/commands/expiretime/
func (m *Miniredis) cmdExpireTime(c *server.Peer, cmd string, args []string) {
if len(args) != 1 {
setDirty(c)
c.WriteError(errWrongNumber(cmd))
return
}

if !m.handleAuth(c) {
return
}
if m.checkPubsub(c, cmd) {
return
}

key := args[0]
withTx(m, c, func(c *server.Peer, ctx *connCtx) {
db := m.db(ctx.selectedDB)

if _, ok := db.keys[key]; !ok {
c.WriteInt(expiretimeReplyMissingKey)
// [expiretime]: https://redis.io/commands/expiretime/
// [pexpiretime]: https://redis.io/commands/pexpiretime/
func (m *Miniredis) makeCmdExpireTime(timeResultStrategy func(time.Time) int) server.Cmd {
return func(c *server.Peer, cmd string, args []string) {
if len(args) != 1 {
setDirty(c)
c.WriteError(errWrongNumber(cmd))
return
}

ttl, ok := db.ttl[key]
if !ok {
c.WriteInt(expiretimeReplyNoExpiration)
if !m.handleAuth(c) {
return
}
if m.checkPubsub(c, cmd) {
return
}

c.WriteInt(int(m.effectiveNow().Add(ttl).Unix()))
})
key := args[0]
withTx(m, c, func(c *server.Peer, ctx *connCtx) {
db := m.db(ctx.selectedDB)

if _, ok := db.keys[key]; !ok {
c.WriteInt(expiretimeReplyMissingKey)
return
}

ttl, ok := db.ttl[key]
if !ok {
c.WriteInt(expiretimeReplyNoExpiration)
return
}

c.WriteInt(timeResultStrategy(m.effectiveNow().Add(ttl)))
})
}
}

// TOUCH
Expand Down
26 changes: 26 additions & 0 deletions cmd_generic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,32 @@ func TestExpireTime(t *testing.T) {
})
}

func TestPExpireTime(t *testing.T) {
s, err := Run()
ok(t, err)
defer s.Close()
c, err := proto.Dial(s.Addr())
ok(t, err)
defer c.Close()

t.Run("nosuch", func(t *testing.T) {
mustDo(t, c, "PEXPIRETIME", "nosuch", proto.Int(-2))
})

t.Run("noexpire", func(t *testing.T) {
s.Set("noexpire", "")
mustDo(t, c, "PEXPIRETIME", "noexpire", proto.Int(-1))
})

t.Run("", func(t *testing.T) {
s.Set("foo", "")
must1(t, c, "PEXPIREAT", "foo", "10413792000123") // Mon Jan 01 2300 00:00:00.123 GMT+0000
mustDo(t, c, "PEXPIRETIME", "foo",
proto.Int(10413792000123),
)
})
}

func TestExists(t *testing.T) {
s, err := Run()
ok(t, err)
Expand Down
7 changes: 6 additions & 1 deletion integration/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,21 @@ func TestExpire(t *testing.T) {
skip(t)
testRaw(t, func(c *client) {
c.Do("EXPIRETIME", "missing")
c.Do("PEXPIRETIME", "missing")

c.Do("SET", "foo", "bar")
c.Do("EXPIRETIME", "foo")
c.Do("PEXPIRETIME", "foo")

c.Do("EXPIRE", "foo", "12")
c.Do("TTL", "foo")
c.Do("TTL", "nosuch")
c.Do("SET", "foo", "bar")
c.Do("PEXPIRE", "foo", "999999")
c.Do("EXPIREAT", "foo", "2234567890")
c.Do("PEXPIREAT", "foo", "2234567890123")
c.Do("EXPIRETIME", "foo")
c.Do("PEXPIREAT", "foo", "2234567890000")
c.Do("PEXPIRETIME", "foo")
// c.Do("PTTL", "foo")
c.Do("PTTL", "nosuch")

Expand Down Expand Up @@ -229,6 +232,8 @@ func TestExpire(t *testing.T) {

c.Error("wrong number", "EXPIRETIME")
c.Error("wrong number", "EXPIRETIME", "too", "many")
c.Error("wrong number", "PEXPIRETIME")
c.Error("wrong number", "PEXPIRETIME", "too", "many")
})
}

Expand Down

0 comments on commit 3d911c1

Please sign in to comment.