Skip to content

Commit 3d911c1

Browse files
wszaranskialicebob
authored andcommitted
Add PEXPIRETIME command
Redis documentation: https://redis.io/commands/pexpiretime/ Signed-off-by: Wojciech Szarański <wojciech.szaranski@gmail.com>
1 parent 1033c7e commit 3d911c1

3 files changed

Lines changed: 79 additions & 32 deletions

File tree

cmd_generic.go

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,23 @@ import (
1313
)
1414

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

22+
func inSeconds(t time.Time) int {
23+
return int(t.Unix())
24+
}
25+
26+
func inMilliSeconds(t time.Time) int {
27+
// Time.UnixMilli() was added in go 1.17
28+
// return int(t.UnixNano() / 1000000) is limited to dates between year 1678 and 2262
29+
// by using following calculation we extend this time without too much complexity
30+
return int(t.Unix())*1000 + t.Nanosecond()/1000000
31+
}
32+
2233
// commandsGeneric handles EXPIRE, TTL, PERSIST, &c.
2334
func commandsGeneric(m *Miniredis) {
2435
m.srv.Register("COPY", m.cmdCopy)
@@ -27,7 +38,8 @@ func commandsGeneric(m *Miniredis) {
2738
m.srv.Register("EXISTS", m.cmdExists)
2839
m.srv.Register("EXPIRE", makeCmdExpire(m, false, time.Second))
2940
m.srv.Register("EXPIREAT", makeCmdExpire(m, true, time.Second))
30-
m.srv.Register("EXPIRETIME", m.cmdExpireTime)
41+
m.srv.Register("EXPIRETIME", m.makeCmdExpireTime(inSeconds))
42+
m.srv.Register("PEXPIRETIME", m.makeCmdExpireTime(inMilliSeconds))
3143
m.srv.Register("KEYS", m.cmdKeys)
3244
// MIGRATE
3345
m.srv.Register("MOVE", m.cmdMove)
@@ -153,41 +165,45 @@ func makeCmdExpire(m *Miniredis, unix bool, d time.Duration) func(*server.Peer,
153165
}
154166
}
155167

156-
// cmdExpireTime returns the absolute Unix timestamp (since January 1, 1970) in seconds at which the given key will expire.
157-
// See [redis documentation].
168+
// makeCmdExpireTime creates server command function that returns the absolute Unix timestamp (since January 1, 1970)
169+
// at which the given key will expire, in unit selected by time result strategy (e.g. seconds, milliseconds).
170+
// For more information see redis documentation for [expiretime] and [pexpiretime].
158171
//
159-
// [redis documentation]: https://redis.io/commands/expiretime/
160-
func (m *Miniredis) cmdExpireTime(c *server.Peer, cmd string, args []string) {
161-
if len(args) != 1 {
162-
setDirty(c)
163-
c.WriteError(errWrongNumber(cmd))
164-
return
165-
}
166-
167-
if !m.handleAuth(c) {
168-
return
169-
}
170-
if m.checkPubsub(c, cmd) {
171-
return
172-
}
173-
174-
key := args[0]
175-
withTx(m, c, func(c *server.Peer, ctx *connCtx) {
176-
db := m.db(ctx.selectedDB)
177-
178-
if _, ok := db.keys[key]; !ok {
179-
c.WriteInt(expiretimeReplyMissingKey)
172+
// [expiretime]: https://redis.io/commands/expiretime/
173+
// [pexpiretime]: https://redis.io/commands/pexpiretime/
174+
func (m *Miniredis) makeCmdExpireTime(timeResultStrategy func(time.Time) int) server.Cmd {
175+
return func(c *server.Peer, cmd string, args []string) {
176+
if len(args) != 1 {
177+
setDirty(c)
178+
c.WriteError(errWrongNumber(cmd))
180179
return
181180
}
182181

183-
ttl, ok := db.ttl[key]
184-
if !ok {
185-
c.WriteInt(expiretimeReplyNoExpiration)
182+
if !m.handleAuth(c) {
183+
return
184+
}
185+
if m.checkPubsub(c, cmd) {
186186
return
187187
}
188188

189-
c.WriteInt(int(m.effectiveNow().Add(ttl).Unix()))
190-
})
189+
key := args[0]
190+
withTx(m, c, func(c *server.Peer, ctx *connCtx) {
191+
db := m.db(ctx.selectedDB)
192+
193+
if _, ok := db.keys[key]; !ok {
194+
c.WriteInt(expiretimeReplyMissingKey)
195+
return
196+
}
197+
198+
ttl, ok := db.ttl[key]
199+
if !ok {
200+
c.WriteInt(expiretimeReplyNoExpiration)
201+
return
202+
}
203+
204+
c.WriteInt(timeResultStrategy(m.effectiveNow().Add(ttl)))
205+
})
206+
}
191207
}
192208

193209
// TOUCH

cmd_generic_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,32 @@ func TestExpireTime(t *testing.T) {
388388
})
389389
}
390390

391+
func TestPExpireTime(t *testing.T) {
392+
s, err := Run()
393+
ok(t, err)
394+
defer s.Close()
395+
c, err := proto.Dial(s.Addr())
396+
ok(t, err)
397+
defer c.Close()
398+
399+
t.Run("nosuch", func(t *testing.T) {
400+
mustDo(t, c, "PEXPIRETIME", "nosuch", proto.Int(-2))
401+
})
402+
403+
t.Run("noexpire", func(t *testing.T) {
404+
s.Set("noexpire", "")
405+
mustDo(t, c, "PEXPIRETIME", "noexpire", proto.Int(-1))
406+
})
407+
408+
t.Run("", func(t *testing.T) {
409+
s.Set("foo", "")
410+
must1(t, c, "PEXPIREAT", "foo", "10413792000123") // Mon Jan 01 2300 00:00:00.123 GMT+0000
411+
mustDo(t, c, "PEXPIRETIME", "foo",
412+
proto.Int(10413792000123),
413+
)
414+
})
415+
}
416+
391417
func TestExists(t *testing.T) {
392418
s, err := Run()
393419
ok(t, err)

integration/string_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,18 +168,21 @@ func TestExpire(t *testing.T) {
168168
skip(t)
169169
testRaw(t, func(c *client) {
170170
c.Do("EXPIRETIME", "missing")
171+
c.Do("PEXPIRETIME", "missing")
171172

172173
c.Do("SET", "foo", "bar")
173174
c.Do("EXPIRETIME", "foo")
175+
c.Do("PEXPIRETIME", "foo")
174176

175177
c.Do("EXPIRE", "foo", "12")
176178
c.Do("TTL", "foo")
177179
c.Do("TTL", "nosuch")
178180
c.Do("SET", "foo", "bar")
179181
c.Do("PEXPIRE", "foo", "999999")
180182
c.Do("EXPIREAT", "foo", "2234567890")
183+
c.Do("PEXPIREAT", "foo", "2234567890123")
181184
c.Do("EXPIRETIME", "foo")
182-
c.Do("PEXPIREAT", "foo", "2234567890000")
185+
c.Do("PEXPIRETIME", "foo")
183186
// c.Do("PTTL", "foo")
184187
c.Do("PTTL", "nosuch")
185188

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

230233
c.Error("wrong number", "EXPIRETIME")
231234
c.Error("wrong number", "EXPIRETIME", "too", "many")
235+
c.Error("wrong number", "PEXPIRETIME")
236+
c.Error("wrong number", "PEXPIRETIME", "too", "many")
232237
})
233238
}
234239

0 commit comments

Comments
 (0)