// 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) } }) }
// 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) } }) }
// 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) } }) }
// 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) } }) }
// 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) } }) }
// 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) } }) }
// 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)) } }) }
// 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) } }) }
// EXEC func (m *Miniredis) cmdExec(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 } ctx := getCtx(r.Client()) if !inTx(ctx) { return redeo.ClientError("EXEC without MULTI") } if dirtyTx(ctx) { out.WriteErrorString("EXECABORT Transaction discarded because of previous errors.") return nil } m.Lock() defer m.Unlock() // Check WATCHed keys. for t, version := range ctx.watch { if m.db(t.db).keyVersion[t.key] > version { // Abort! Abort! stopTx(ctx) out.WriteBulkLen(0) return nil } } out.WriteBulkLen(len(ctx.transaction)) for _, cb := range ctx.transaction { cb(out, ctx) } // We're done stopTx(ctx) return nil }
// 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) } }) }
// 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)) } }) }
// 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]) } }) }
// ZRANGEBYLEX func (m *Miniredis) cmdZrangebylex(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] min, minIncl, err := parseLexrange(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(err.Error()) return nil } max, maxIncl, err := parseLexrange(r.Args[2]) if err != nil { setDirty(r.Client()) out.WriteErrorString(err.Error()) return nil } args := r.Args[3:] withLimit := false limitStart := 0 limitEnd := 0 for len(args) > 0 { if strings.ToLower(args[0]) == "limit" { withLimit = true args = args[1:] if len(args) < 2 { out.WriteErrorString(msgSyntaxError) return nil } limitStart, err = strconv.Atoi(args[0]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } limitEnd, err = strconv.Atoi(args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } args = args[2:] continue } // Syntax error setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } 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) != "zset" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.ssetMembers(key) // Just key sort. If scores are not the same we don't care. sort.Strings(members) members = withLexRange(members, min, minIncl, max, maxIncl) // Apply LIMIT ranges. That's <start> <elements>. Unlike RANGE. if withLimit { if limitStart < 0 { members = nil } else { if limitStart < len(members) { members = members[limitStart:] } else { // out of range members = nil } if limitEnd >= 0 { if len(members) > limitEnd { members = members[:limitEnd] } } } } out.WriteBulkLen(len(members)) for _, el := range members { out.WriteString(el) } }) }