diff --git a/cmd_hash.go b/cmd_hash.go index 0663283..1a82938 100644 --- a/cmd_hash.go +++ b/cmd_hash.go @@ -4,6 +4,7 @@ package miniredis import ( "math/big" + "math/rand" "strconv" "strings" @@ -700,16 +701,17 @@ func (m *Miniredis) cmdHrandfield(c *server.Peer, cmd string, args []string) { opts := struct { key string count int + countSet bool withValues bool }{ - key: args[0], - count: 1, + key: args[0], } if len(args) > 1 { if ok := optIntErr(c, args[1], &opts.count, msgInvalidInt); !ok { return } + opts.countSet = true } if len(args) == 3 { @@ -723,42 +725,54 @@ func (m *Miniredis) cmdHrandfield(c *server.Peer, cmd string, args []string) { } withTx(m, c, func(peer *server.Peer, ctx *connCtx) { - if opts.count == 0 { - peer.WriteLen(0) - return - } db := m.db(ctx.selectedDB) members := db.hashFields(opts.key) - // if cnt positive - cnt := 0 - iterCnt := len(members) - if opts.count > 0 { - if opts.count > len(members) { - cnt = len(members) - } else { - cnt = opts.count + m.shuffle(members) + + if !opts.countSet { + // > When called with just the key argument, return a random field from the + // hash value stored at key. + if len(members) == 0 { + peer.WriteNull() + return } - } else { - cnt = -opts.count - iterCnt *= cnt + peer.WriteBulk(members[0]) + return } - p := m.randPerm(iterCnt) - if opts.withValues { - peer.WriteMapLen(cnt) - for i := 0; i < cnt; i++ { - idx := p[i] % len(members) - peer.WriteBulk(members[idx]) - peer.WriteBulk(db.hashGet(opts.key, members[idx])) + if len(members) > abs(opts.count) { + members = members[:abs(opts.count)] + } + switch { + case opts.count >= 0: + // if count is positive there can't be duplicates, and the length is restricted + case opts.count < 0: + // if count is negative there can be duplicates, but length will match + if len(members) > 0 { + for len(members) < -opts.count { + members = append(members, members[rand.Intn(len(members))]) + } } - return - } else { - peer.WriteLen(cnt) - for i := 0; i < cnt; i++ { - idx := p[i] % len(members) - peer.WriteBulk(members[idx]) + } + + if opts.withValues { + peer.WriteMapLen(len(members)) + for _, m := range members { + peer.WriteBulk(m) + peer.WriteBulk(db.hashGet(opts.key, m)) } return } + peer.WriteLen(len(members)) + for _, m := range members { + peer.WriteBulk(m) + } }) } + +func abs(n int) int { + if n < 0 { + return -n + } + return n +} diff --git a/integration/hash_test.go b/integration/hash_test.go index 8907c3d..198b1ca 100644 --- a/integration/hash_test.go +++ b/integration/hash_test.go @@ -233,10 +233,40 @@ func TestHstrlen(t *testing.T) { func TestHrandfield(t *testing.T) { skip(t) testRaw(t, func(c *client) { - // A random key from a DB with a single key. We can test that. c.Do("HSET", "one", "foo", "bar") + c.Do("HRANDFIELD", "one") + c.Do("HRANDFIELD", "one", "0") c.Do("HRANDFIELD", "one", "1") + c.Do("HRANDFIELD", "one", "2") // limited to 1 + c.Do("HRANDFIELD", "one", "3") // limited to 1 + c.Do("HRANDFIELD", "one", "-1") + c.Do("HRANDFIELD", "one", "-2") // padded + c.Do("HRANDFIELD", "one", "-3") // padded + + c.Do("HSET", "more", "foo", "bar", "baz", "bak") + c.DoLoosely("HRANDFIELD", "more") + c.Do("HRANDFIELD", "more", "0") + c.DoLoosely("HRANDFIELD", "more", "1") + c.DoLoosely("HRANDFIELD", "more", "2") + c.DoLoosely("HRANDFIELD", "more", "3") // limited to 2 + c.DoLoosely("HRANDFIELD", "more", "-1") + c.DoLoosely("HRANDFIELD", "more", "-2") + c.DoLoosely("HRANDFIELD", "more", "-3") // length padded to 3 + + c.Do("HRANDFIELD", "nosuch", "1") + c.Do("HRANDFIELD", "nosuch", "2") + c.Do("HRANDFIELD", "nosuch", "3") + c.Do("HRANDFIELD", "nosuch", "0") + c.Do("HRANDFIELD", "nosuch") + c.Do("HRANDFIELD", "nosuch", "-1") // still empty + c.Do("HRANDFIELD", "nosuch", "-2") // still empty + c.Do("HRANDFIELD", "nosuch", "-3") // still empty + c.DoLoosely("HRANDFIELD", "one", "2") + c.DoLoosely("HRANDFIELD", "one", "7") + c.DoLoosely("HRANDFIELD", "one", "2", "WITHVALUE") + c.DoLoosely("HRANDFIELD", "one", "7", "WITHVALUE") c.Error("ERR syntax error", "HRANDFIELD", "foo", "1", "2") + c.Error("ERR wrong number", "HRANDFIELD") }) }