// SPOP func (m *Miniredis) cmdSpop(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'spop' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteNil() return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.setMembers(key) member := members[rand.Intn(len(members))] db.setRem(key, member) out.WriteString(member) }) }
// HGET func (m *Miniredis) cmdHget(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] field := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) t, ok := db.keys[key] if !ok { out.WriteNil() return } if t != "hash" { out.WriteErrorString(msgWrongType) return } value, ok := db.hashKeys[key][field] if !ok { out.WriteNil() return } out.WriteString(value) }) }
// RPOPLPUSH func (m *Miniredis) cmdRpoplpush(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } src := r.Args[0] dst := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(src) { out.WriteNil() return } if db.t(src) != "list" || (db.exists(dst) && db.t(dst) != "list") { out.WriteErrorString(msgWrongType) return } elem := db.listPop(src) db.listLpush(dst, elem) out.WriteString(elem) }) }
// HGETALL func (m *Miniredis) cmdHgetall(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) t, ok := db.keys[key] if !ok { out.WriteBulkLen(0) return } if t != "hash" { out.WriteErrorString(msgWrongType) return } out.WriteBulkLen(len(db.hashKeys[key]) * 2) for _, k := range db.hashFields(key) { out.WriteString(k) out.WriteString(db.hashGet(key, k)) } }) }
// ZINCRBY func (m *Miniredis) cmdZincrby(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] delta, err := strconv.ParseFloat(r.Args[1], 64) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidFloat) return nil } member := r.Args[2] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if db.exists(key) && db.t(key) != "zset" { out.WriteErrorString(msgWrongType) return } newScore := db.ssetIncrby(key, member, delta) out.WriteString(formatFloat(newScore)) }) }
// HMGET func (m *Miniredis) cmdHmget(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hmget' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "hash" { out.WriteErrorString(msgWrongType) return } f, ok := db.hashKeys[key] if !ok { f = map[string]string{} } out.WriteBulkLen(len(r.Args) - 1) for _, k := range r.Args[1:] { v, ok := f[k] if !ok { out.WriteNil() continue } out.WriteString(v) } }) }
// SMEMBERS func (m *Miniredis) cmdSmembers(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'smembers' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteBulkLen(0) return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.setMembers(key) out.WriteBulkLen(len(members)) for _, elem := range members { out.WriteString(elem) } }) }
// HVALS func (m *Miniredis) cmdHvals(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hvals' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) t, ok := db.keys[key] if !ok { out.WriteBulkLen(0) return } if t != "hash" { out.WriteErrorString(msgWrongType) return } out.WriteBulkLen(len(db.hashKeys[key])) for _, v := range db.hashKeys[key] { out.WriteString(v) } }) }
// GETRANGE func (m *Miniredis) cmdGetrange(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] start, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } end, err := strconv.Atoi(r.Args[2]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "string" { out.WriteErrorString(msgWrongType) return } v := db.stringKeys[key] out.WriteString(withRange(v, start, end)) }) }
// GETSET func (m *Miniredis) cmdGetset(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] value := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "string" { out.WriteErrorString(msgWrongType) return } old, ok := db.stringKeys[key] db.stringSet(key, value) // a GETSET clears the expire delete(db.expire, key) if !ok { out.WriteNil() return } out.WriteString(old) return }) }
// MGET func (m *Miniredis) cmdMget(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) out.WriteBulkLen(len(r.Args)) for _, k := range r.Args { if t, ok := db.keys[k]; !ok || t != "string" { out.WriteNil() continue } v, ok := db.stringKeys[k] if !ok { // Should not happen, we just checked keys[] out.WriteNil() continue } out.WriteString(v) } }) }
// LPOP func (m *Miniredis) cmdLpop(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'lpop' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { // Non-existing key is fine. out.WriteNil() return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } elem := db.listLpop(key) out.WriteString(elem) }) }
// HINCRBYFLOAT func (m *Miniredis) cmdHincrbyfloat(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hincrbyfloat' command") return nil } key := r.Args[0] field := r.Args[1] delta, err := strconv.ParseFloat(r.Args[2], 64) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidFloat) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "hash" { out.WriteErrorString(msgWrongType) return } v, err := db.hashIncrfloat(key, field, delta) if err != nil { out.WriteErrorString(err.Error()) return } out.WriteString(formatFloat(v)) }) }
// RPOP func (m *Miniredis) cmdRpop(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteNil() return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } elem := db.listPop(key) out.WriteString(elem) }) }
// SDIFF func (m *Miniredis) cmdSdiff(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } keys := r.Args return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) set, err := db.setDiff(keys) if err != nil { out.WriteErrorString(err.Error()) return } out.WriteBulkLen(len(set)) for k := range set { out.WriteString(k) } }) }
// ZSCORE func (m *Miniredis) cmdZscore(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] member := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteNil() return } if db.t(key) != "zset" { out.WriteErrorString(ErrWrongType.Error()) return } if !db.ssetExists(key, member) { out.WriteNil() return } out.WriteString(formatFloat(db.ssetScore(key, member))) }) }
// HGET func (m *Miniredis) cmdHget(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hget' command") return nil } key := r.Args[0] field := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) t, ok := db.keys[key] if !ok { out.WriteNil() return } if t != "hash" { out.WriteErrorString(msgWrongType) return } value, ok := db.hashKeys[key][field] if !ok { out.WriteNil() return } out.WriteString(value) }) }
// INCRBYFLOAT func (m *Miniredis) cmdIncrbyfloat(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] delta, err := strconv.ParseFloat(r.Args[1], 64) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidFloat) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "string" { out.WriteErrorString(msgWrongType) return } v, err := db.stringIncrfloat(key, delta) if err != nil { out.WriteErrorString(err.Error()) return } // Don't touch TTL out.WriteString(formatFloat(v)) }) }
// HKEYS func (m *Miniredis) cmdHkeys(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hkeys' command") return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteBulkLen(0) return } if db.t(key) != "hash" { out.WriteErrorString(msgWrongType) return } fields := db.hashFields(key) out.WriteBulkLen(len(fields)) for _, f := range fields { out.WriteString(f) } }) }
// RANDOMKEY func (m *Miniredis) cmdRandomkey(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 0 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if len(db.keys) == 0 { out.WriteNil() return } nr := rand.Intn(len(db.keys)) for k := range db.keys { if nr == 0 { out.WriteString(k) return } nr-- } }) }
// ECHO func (m *Miniredis) cmdEcho(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("usage error") return nil } msg := r.Args[0] out.WriteString(msg) return nil }
// ECHO func (m *Redico) cmdEcho(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } msg := r.Args[0] out.WriteString(msg) return nil }
// LRANGE func (m *Miniredis) cmdLrange(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] start, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } end, err := strconv.Atoi(r.Args[2]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if t, ok := db.keys[key]; ok && t != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] if len(l) == 0 { out.WriteBulkLen(0) return } rs, re := redisRange(len(l), start, end, false) out.WriteBulkLen(re - rs) for _, el := range l[rs:re] { out.WriteString(el) } }) }
// GET func (m *Redico) cmdGet(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteNil() return } out.WriteString(db.stringGet(key)) }) }
// LINDEX func (m *Miniredis) cmdLindex(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] offset, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) t, ok := db.keys[key] if !ok { // No such key out.WriteNil() return } if t != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] if offset < 0 { offset = len(l) + offset } if offset < 0 || offset > len(l)-1 { out.WriteNil() return } out.WriteString(l[offset]) }) }
// KEYSSTART func (m *Redico) cmdKeysStart(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) keys := db.keyStart(key) out.WriteBulkLen(len(keys)) for _, s := range keys { out.WriteString(s) } }) }
// SDIFF func (m *Miniredis) cmdSdiff(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'sdiff' command") return nil } keys := r.Args return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) set, err := db.setDiff(keys) if err != nil { out.WriteErrorString(err.Error()) return } out.WriteBulkLen(len(set)) for k := range set { out.WriteString(k) } }) }
// SRANDMEMBER func (m *Miniredis) cmdSrandmember(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'srandmember' command") return nil } if len(r.Args) > 2 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } key := r.Args[0] count := 0 withCount := false if len(r.Args) == 2 { var err error count, err = strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } withCount = true } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteNil() return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.setMembers(key) if count < 0 { // Non-unique elements is allowed with negative count. out.WriteBulkLen(-count) for count != 0 { member := members[rand.Intn(len(members))] out.WriteString(member) count++ } return } // Must be unique elements. shuffle(members) if count > len(members) { count = len(members) } if !withCount { out.WriteString(members[0]) return } out.WriteBulkLen(count) for i := range make([]struct{}, count) { out.WriteString(members[i]) } }) }
// SCAN func (m *Redico) cmdScan(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } cursor, err := strconv.Atoi(r.Args[0]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidCursor) return nil } // MATCH and COUNT options var withMatch bool var match string args := r.Args[1:] for len(args) > 0 { if strings.ToLower(args[0]) == "count" { if len(args) < 2 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } _, err := strconv.Atoi(args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } // We do nothing with count. args = args[2:] continue } if strings.ToLower(args[0]) == "match" { if len(args) < 2 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } withMatch = true match = args[1] args = args[2:] continue } setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) // We return _all_ (matched) keys every time. if cursor != 0 { // Invalid cursor. out.WriteBulkLen(2) out.WriteString("0") // no next cursor out.WriteBulkLen(0) // no elements return } keys := db.allKeys() if withMatch { keys = matchKeys(keys, match) } out.WriteBulkLen(2) out.WriteString("0") // no next cursor out.WriteBulkLen(len(keys)) for _, k := range keys { out.WriteString(k) } }) }
// HSCAN func (m *Miniredis) cmdHscan(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hscan' command") return nil } key := r.Args[0] cursor, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidCursor) return nil } // MATCH and COUNT options var withMatch bool var match string args := r.Args[2:] for len(args) > 0 { if strings.ToLower(args[0]) == "count" { if len(args) < 2 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } _, err := strconv.Atoi(args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } // We do nothing with count. args = args[2:] continue } if strings.ToLower(args[0]) == "match" { if len(args) < 2 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } withMatch = true match = args[1] args = args[2:] continue } setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) // We return _all_ (matched) keys every time. if cursor != 0 { // Invalid cursor. out.WriteBulkLen(2) out.WriteString("0") // no next cursor out.WriteBulkLen(0) // no elements return } if db.exists(key) && db.t(key) != "hash" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.hashFields(key) if withMatch { members = matchKeys(members, match) } out.WriteBulkLen(2) out.WriteString("0") // no next cursor // HSCAN gives key, values. out.WriteBulkLen(len(members) * 2) for _, k := range members { out.WriteString(k) out.WriteString(db.hashGet(key, k)) } }) }