Skip to content

Commit

Permalink
Merge pull request alicebob#264 from dntj/master
Browse files Browse the repository at this point in the history
Implement GETEX.
  • Loading branch information
alicebob authored Apr 27, 2022
2 parents 6550e6c + 11fc03d commit 71881f2
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Implemented commands:
- GETRANGE
- GETSET
- GETDEL
- GETEX
- INCR
- INCRBY
- INCRBYFLOAT
Expand Down
88 changes: 88 additions & 0 deletions cmd_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func commandsString(m *Miniredis) {
m.srv.Register("DECR", m.cmdDecr)
m.srv.Register("GETBIT", m.cmdGetbit)
m.srv.Register("GET", m.cmdGet)
m.srv.Register("GETEX", m.cmdGetex)
m.srv.Register("GETRANGE", m.cmdGetrange)
m.srv.Register("GETSET", m.cmdGetset)
m.srv.Register("GETDEL", m.cmdGetdel)
Expand Down Expand Up @@ -394,6 +395,93 @@ func (m *Miniredis) cmdGet(c *server.Peer, cmd string, args []string) {
})
}

// GETEX
func (m *Miniredis) cmdGetex(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
}

var opts struct {
key string
ttl time.Duration
persist bool // remove existing TTL on the key.
}

opts.key, args = args[0], args[1:]
if len(args) > 0 {
timeUnit := time.Second
switch arg := strings.ToUpper(args[0]); arg {
case "PERSIST":
if len(args) > 1 {
setDirty(c)
c.WriteError(msgSyntaxError)
return
}
opts.persist = true
case "PX", "PXAT":
timeUnit = time.Millisecond
fallthrough
case "EX", "EXAT":
if len(args) != 2 {
setDirty(c)
c.WriteError(msgSyntaxError)
return
}
expire, err := strconv.Atoi(args[1])
if err != nil {
setDirty(c)
c.WriteError(msgInvalidInt)
return
}
if expire <= 0 {
setDirty(c)
c.WriteError(msgInvalidSETime)
return
}

if arg == "PXAT" || arg == "EXAT" {
opts.ttl = m.at(expire, timeUnit)
} else {
opts.ttl = time.Duration(expire) * timeUnit
}
default:
setDirty(c)
c.WriteError(msgSyntaxError)
return
}
}

withTx(m, c, func(c *server.Peer, ctx *connCtx) {
db := m.db(ctx.selectedDB)

if !db.exists(opts.key) {
c.WriteNull()
return
}
switch {
case opts.persist:
delete(db.ttl, opts.key)
case opts.ttl != 0:
db.ttl[opts.key] = opts.ttl
}

if db.t(opts.key) != "string" {
c.WriteError(msgWrongType)
return
}

c.WriteBulk(db.stringGet(opts.key))
})
}

// GETSET
func (m *Miniredis) cmdGetset(c *server.Peer, cmd string, args []string) {
if len(args) != 2 {
Expand Down
80 changes: 80 additions & 0 deletions cmd_string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,86 @@ func TestDecr(t *testing.T) {
}
}

func TestGetex(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("basic", func(t *testing.T) {
// Missing key
mustNil(t, c, "GETEX", "basic")

// Existing key
s.Set("basic", "bar")
mustDo(t, c, "GETEX", "basic", proto.String("bar"))
equals(t, time.Duration(0), s.TTL("basic"))
})

t.Run("EX", func(t *testing.T) {
// Missing key
mustNil(t, c, "GETEX", "withex", "EX", "123")

// Existing key
s.Set("withex", "bar")
mustDo(t, c, "GETEX", "withex", "EX", "123", proto.String("bar"))
equals(t, 123*time.Second, s.TTL("withex"))
})

t.Run("PX", func(t *testing.T) {
// Missing key
mustNil(t, c, "GETEX", "withpx", "PX", "123")

// Existing key
s.Set("withpx", "bar")
mustDo(t, c, "GETEX", "withpx", "PX", "123", proto.String("bar"))
equals(t, 123*time.Millisecond, s.TTL("withpx"))
})

t.Run("EXAT", func(t *testing.T) {
s.SetTime(time.Unix(100, 0))

// Missing key
mustNil(t, c, "GETEX", "withexat", "EXAT", "123")

// Existing key
s.Set("withexat", "bar")
mustDo(t, c, "GETEX", "withexat", "EXAT", "123", proto.String("bar"))
equals(t, 23*time.Second, s.TTL("withexat"))
})

t.Run("PXAT", func(t *testing.T) {
s.SetTime(time.Unix(0, 100_000_000))

// Missing key
mustNil(t, c, "GETEX", "withpxat", "PXAT", "123")

// Existing key
s.Set("withpxat", "bar")
mustDo(t, c, "GETEX", "withpxat", "PXAT", "123", proto.String("bar"))
equals(t, 23*time.Millisecond, s.TTL("withpxat"))
})

t.Run("PERSIST", func(t *testing.T) {
// Missing key
mustNil(t, c, "GETEX", "foo", "PERSIST")

// Existing key with TTL
mustOK(t, c, "SETEX", "foo", "123", "bar")
mustDo(t, c, "GETEX", "foo", "PERSIST", proto.String("bar"))
equals(t, time.Duration(0), s.TTL("foo"))
})

t.Run("errors", func(t *testing.T) {
mustDo(t, c,
"GETEX", "one", "two",
proto.Error(msgSyntaxError),
)
})
}

func TestGetSet(t *testing.T) {
s, err := Run()
ok(t, err)
Expand Down
1 change: 1 addition & 0 deletions integration/pubsub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func TestPubsubMode(t *testing.T) {
c.Error("are allowed", "FLUSHALL")
c.Error("are allowed", "FLUSHDB")
c.Error("are allowed", "GET", "foo")
c.Error("are allowed", "GETEX", "foo")
c.Error("are allowed", "GETBIT", "foo", "12")
c.Error("are allowed", "GETRANGE", "foo", "12", "12")
c.Error("are allowed", "GETSET", "foo", "bar")
Expand Down
29 changes: 29 additions & 0 deletions integration/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,35 @@ func TestStringGetSet(t *testing.T) {
})
}

func TestStringGetex(t *testing.T) {
testRaw(t, func(c *client) {
c.Do("GETEX", "missing")

c.Do("SET", "foo", "bar")
c.Do("GETEX", "foo")
c.Do("TTL", "foo")

c.Do("GETEX", "foo", "EX", "10")
c.Do("TTL", "foo")

// Failure cases
c.Error("wrong number", "GETEX")
c.Error("syntax error", "GETEX", "foo", "bar")
c.Error("syntax error", "GETEX", "foo", "EX", "10", "PERSIST")
c.Error("syntax error", "GETEX", "foo", "EX", "10", "PX", "10")
c.Error("not an integer", "GETEX", "foo", "EX", "ten")

// Wrong type
c.Do("HSET", "hash", "key", "value")
c.Error("wrong kind", "GETEX", "hash")

c.Do("SET", "hittl", "bar")
c.Do("PEXPIRE", "hittl", "999999")
c.Do("GETEX", "hittl", "PERSIST")
c.Do("TTL", "hittl")
})
}

func TestStringGetdel(t *testing.T) {
testRaw(t, func(c *client) {
c.Do("GETDEL", "missing")
Expand Down
1 change: 1 addition & 0 deletions integration/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func TestTx(t *testing.T) {
testRaw(t, func(c *client) {
c.Do("MULTI")
c.Do("GET", "str")
c.Do("GETEX", "str")
c.Do("SET", "str", "bar")
c.Do("SETNX", "str", "bar")
c.Do("GETSET", "str", "bar")
Expand Down

0 comments on commit 71881f2

Please sign in to comment.