// DECRBY func (m *Miniredis) cmdDecrby(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.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) if t, ok := db.keys[key]; ok && t != "string" { out.WriteErrorString(msgWrongType) return } v, err := db.stringIncr(key, -delta) if err != nil { out.WriteErrorString(err.Error()) return } // Don't touch TTL out.WriteInt(v) }) }
// RPUSHX func (m *Miniredis) cmdRpushx(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 !db.exists(key) { out.WriteZero() return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } newLen := db.listPush(key, value) out.WriteInt(newLen) }) }
// LPUSHX func (m *Miniredis) cmdLpushx(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'lpushx' command") 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 !db.exists(key) { out.WriteZero() return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } newLen := db.listLpush(key, value) out.WriteInt(newLen) }) }
// SCARD func (m *Miniredis) cmdScard(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'scard' 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.WriteZero() return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.setMembers(key) out.WriteInt(len(members)) }) }
// HINCRBY func (m *Miniredis) cmdHincrby(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hincrby' command") return nil } key := r.Args[0] field := r.Args[1] delta, 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 != "hash" { out.WriteErrorString(msgWrongType) return } v, err := db.hashIncr(key, field, delta) if err != nil { out.WriteErrorString(err.Error()) return } out.WriteInt(v) }) }
// LINSERT func (m *Miniredis) cmdLinsert(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 4 { setDirty(r.Client()) return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } key := r.Args[0] where := 0 switch strings.ToLower(r.Args[1]) { case "before": where = -1 case "after": where = +1 default: setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } pivot := r.Args[2] value := r.Args[3] 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.WriteZero() return } if t != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] for i, el := range l { if el != pivot { continue } if where < 0 { l = append(l[:i], append(listKey{value}, l[i:]...)...) } else { if i == len(l)-1 { l = append(l, value) } else { l = append(l[:i+1], append(listKey{value}, l[i+1:]...)...) } } db.listKeys[key] = l db.keyVersion[key]++ out.WriteInt(len(l)) return } out.WriteInt(-1) }) }
// ZREM func (m *Miniredis) cmdZrem(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] members := r.Args[1:] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteZero() return } if db.t(key) != "zset" { out.WriteErrorString(ErrWrongType.Error()) return } deleted := 0 for _, member := range members { if db.ssetRem(key, member) { deleted++ } } out.WriteInt(deleted) }) }
// SREM func (m *Miniredis) cmdSrem(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'srem' command") return nil } key := r.Args[0] fields := r.Args[1:] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteInt(0) return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } out.WriteInt(db.setRem(key, fields...)) }) }
// RPUSH func (m *Miniredis) cmdRpush(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] args := r.Args[1:] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if db.exists(key) && db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } var newLen int for _, value := range args { newLen = db.listPush(key, value) } out.WriteInt(newLen) }) }
// SCARD func (m *Miniredis) cmdScard(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.WriteZero() return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.setMembers(key) out.WriteInt(len(members)) }) }
// INCR func (m *Redico) cmdIncr(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) key := r.Args[0] if !db.exists(key) { out.WriteErrorString(msgWrongType) return } v, err := db.stringIncr(key, +1) if err != nil { out.WriteErrorString(err.Error()) return } // Don't touch TTL out.WriteInt(v) }) }
// SUNIONSTORE func (m *Miniredis) cmdSunionstore(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 } dest := r.Args[0] keys := r.Args[1:] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) set, err := db.setUnion(keys) if err != nil { out.WriteErrorString(err.Error()) return } db.del(dest, true) db.setSet(dest, set) out.WriteInt(len(set)) }) }
// SREM func (m *Miniredis) cmdSrem(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] fields := r.Args[1:] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteInt(0) return } if db.t(key) != "set" { out.WriteErrorString(ErrWrongType.Error()) return } out.WriteInt(db.setRem(key, fields...)) }) }
// LLEN func (m *Miniredis) cmdLlen(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 { // No such key. That's zero length. out.WriteZero() return } if t != "list" { out.WriteErrorString(msgWrongType) return } out.WriteInt(len(db.listKeys[key])) }) }
// APPEND func (m *Miniredis) cmdAppend(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 } newValue := db.stringKeys[key] + value db.stringSet(key, newValue) out.WriteInt(len(newValue)) }) }
// BITCOUNT func (m *Miniredis) cmdBitcount(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 } var ( key = r.Args[0] useRange = false start, end = 0, 0 args = r.Args[1:] ) if len(args) >= 2 { useRange = true var err error start, err = strconv.Atoi(args[0]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } end, err = strconv.Atoi(args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } args = args[2:] } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteZero() return } if db.t(key) != "string" { out.WriteErrorString(msgWrongType) return } // Real redis only checks after it knows the key is there and a string. if len(args) != 0 { out.WriteErrorString(msgSyntaxError) return } v := db.stringKeys[key] if useRange { v = withRange(v, start, end) } out.WriteInt(countBits([]byte(v))) }) }
// LREM func (m *Miniredis) cmdLrem(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'lrem' command") return nil } key := r.Args[0] count, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } value := r.Args[2] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteZero() return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] if count < 0 { reverseSlice(l) } deleted := 0 newL := []string{} toDelete := len(l) if count < 0 { toDelete = -count } if count > 0 { toDelete = count } for _, el := range l { if el == value { if toDelete > 0 { deleted++ toDelete-- continue } } newL = append(newL, el) } if count < 0 { reverseSlice(newL) } db.listKeys[key] = newL db.keyVersion[key]++ out.WriteInt(deleted) }) }
// SETBIT func (m *Miniredis) cmdSetbit(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] bit, err := strconv.Atoi(r.Args[1]) if err != nil || bit < 0 { setDirty(r.Client()) return redeo.ClientError("bit offset is not an integer or out of range") } newBit, err := strconv.Atoi(r.Args[2]) if err != nil || (newBit != 0 && newBit != 1) { setDirty(r.Client()) return redeo.ClientError("bit is not an integer or out of range") } 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 } value := []byte(db.stringKeys[key]) ourByteNr := bit / 8 ourBitNr := bit % 8 if ourByteNr > len(value)-1 { // Too short. Expand. newValue := make([]byte, ourByteNr+1) copy(newValue, value) value = newValue } old := 0 if toBits(value[ourByteNr])[ourBitNr] { old = 1 } if newBit == 0 { value[ourByteNr] &^= 1 << uint8(7-ourBitNr) } else { value[ourByteNr] |= 1 << uint8(7-ourBitNr) } db.stringSet(key, string(value)) out.WriteInt(old) }) }
// DBSIZE func (m *Miniredis) cmdDbsize(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) out.WriteInt(len(db.keys)) }) }
// ZREMRANGEBYLEX func (m *Miniredis) cmdZremrangebylex(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 } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteInt(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) for _, el := range members { db.ssetRem(key, el) } out.WriteInt(len(members)) }) }
// DEL func (m *Redico) cmdDel(out *redeo.Responder, r *redeo.Request) error { if !m.handleAuth(r.Client(), out) { return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) count := 0 for _, key := range r.Args { if db.exists(key) { count++ } db.del(key, true) // delete expire } out.WriteInt(count) }) }
// ZADD func (m *Miniredis) cmdZadd(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] args := r.Args[1:] if len(args)%2 != 0 { setDirty(r.Client()) out.WriteErrorString(msgSyntaxError) return nil } elems := map[string]float64{} for len(args) > 0 { score, err := strconv.ParseFloat(args[0], 64) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidFloat) return nil } elems[args[1]] = score args = 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(ErrWrongType.Error()) return } added := 0 for member, score := range elems { if db.ssetAdd(key, score, member) { added++ } } out.WriteInt(added) }) }
// ZREMRANGEBYSCORE func (m *Miniredis) cmdZremrangebyscore(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 := parseFloatRange(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidMinMax) return nil } max, maxIncl, err := parseFloatRange(r.Args[2]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidMinMax) return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteInt(0) return } if db.t(key) != "zset" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.ssetElements(key) members = withSSRange(members, min, minIncl, max, maxIncl) for _, el := range members { db.ssetRem(key, el.member) } out.WriteInt(len(members)) }) }
// ZREMRANGEBYRANK func (m *Miniredis) cmdZremrangebyrank(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 !db.exists(key) { out.WriteInt(0) return } if db.t(key) != "zset" { out.WriteErrorString(ErrWrongType.Error()) return } members := db.ssetMembers(key) rs, re := redisRange(len(members), start, end, false) for _, el := range members[rs:re] { db.ssetRem(key, el) } out.WriteInt(re - rs) }) }
// HDEL func (m *Miniredis) cmdHdel(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] fields := 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 { // No key is zero deleted out.WriteInt(0) return } if t != "hash" { out.WriteErrorString(msgWrongType) return } deleted := 0 for _, f := range fields { _, ok := db.hashKeys[key][f] if !ok { continue } delete(db.hashKeys[key], f) deleted++ } out.WriteInt(deleted) // Nothing left. Remove the whole key. if len(db.hashKeys[key]) == 0 { db.del(key, true) } }) }
// SETRANGE func (m *Miniredis) cmdSetrange(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] pos, err := strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } if pos < 0 { setDirty(r.Client()) return redeo.ClientError("offset is out of range") } subst := r.Args[2] 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 := []byte(db.stringKeys[key]) if len(v) < pos+len(subst) { newV := make([]byte, pos+len(subst)) copy(newV, v) v = newV } copy(v[pos:pos+len(subst)], subst) db.stringSet(key, string(v)) out.WriteInt(len(v)) }) }
// ZLEXCOUNT func (m *Miniredis) cmdZlexcount(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 3 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'zlexcount' command") 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 } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(key) { out.WriteInt(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) out.WriteInt(len(members)) }) }
// EXISTS func (m *Redico) cmdExists(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) found := 0 for _, k := range r.Args { if db.exists(k) { found++ } } out.WriteInt(found) }) }
// TTL func (m *Miniredis) cmdTTL(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 _, ok := db.keys[key]; !ok { // No such key out.WriteInt(-2) return } value, ok := db.expire[key] if !ok { // No expire value out.WriteInt(-1) return } out.WriteInt(value) }) }
// HEXISTS func (m *Miniredis) cmdHexists(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 2 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hexists' 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.WriteInt(0) return } if t != "hash" { out.WriteErrorString(msgWrongType) return } if _, ok := db.hashKeys[key][field]; !ok { out.WriteInt(0) return } out.WriteInt(1) }) }