// MMSET func (m *Miniredis) cmdHmset(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) < 3 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'hmset' command") return nil } key := r.Args[0] args := r.Args[1:] if len(args)%2 != 0 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for HMSET") 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 } for len(args) > 0 { field := args[0] value := args[1] args = args[2:] db.hashSet(key, field, value) } out.WriteOK() }) }
// PSETEX func (m *Miniredis) cmdPsetex(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] ttl, 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) db.del(key, true) // Clear any existing keys. db.stringSet(key, value) db.expire[key] = ttl // We put millisecond keys in with the second keys. out.WriteOK() }) }
// MSET func (m *Miniredis) cmdMset(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 } if len(r.Args)%2 != 0 { setDirty(r.Client()) // non-default error message out.WriteErrorString("ERR wrong number of arguments for MSET") return nil } return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) for len(r.Args) > 0 { key := r.Args[0] value := r.Args[1] r.Args = r.Args[2:] db.del(key, true) // clear TTL db.stringSet(key, value) } out.WriteOK() }) }
// FLUSHALL func (m *Miniredis) cmdFlushall(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) { m.dbs = map[int]*RedisDB{} out.WriteOK() }) }
// FLUSHDB func (m *Miniredis) cmdFlushdb(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) { delete(m.dbs, ctx.selectedDB) out.WriteOK() }) }
// UNWATCH func (m *Miniredis) cmdUnwatch(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 0 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'unwatch' command") return nil } // Doesn't matter if UNWATCH is in a TX or not. Looks like a Redis bug to me. unwatch(getCtx(r.Client())) return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { // Do nothing if it's called in a transaction. out.WriteOK() }) }
// MULTI func (m *Miniredis) cmdMulti(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 0 { out.WriteErrorString("ERR wrong number of arguments for 'multi' command") return nil } ctx := getCtx(r.Client()) if inTx(ctx) { out.WriteErrorString("ERR MULTI calls can not be nested") return nil } startTx(ctx) out.WriteOK() return nil }
// DISCARD func (m *Miniredis) cmdDiscard(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 0 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'discard' command") return nil } ctx := getCtx(r.Client()) if !inTx(ctx) { out.WriteErrorString("ERR DISCARD without MULTI") return nil } stopTx(ctx) out.WriteOK() return nil }
// UNWATCH func (m *Miniredis) cmdUnwatch(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 } // Doesn't matter if UNWATCH is in a TX or not. Looks like a Redis bug to me. unwatch(getCtx(r.Client())) return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { // Do nothing if it's called in a transaction. out.WriteOK() }) }
// DISCARD func (m *Miniredis) cmdDiscard(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("DISCARD without MULTI") } stopTx(ctx) out.WriteOK() return nil }
// MULTI func (m *Miniredis) cmdMulti(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 0 { return r.WrongNumberOfArgs() } if !m.handleAuth(r.Client(), out) { return nil } ctx := getCtx(r.Client()) if inTx(ctx) { return redeo.ClientError("MULTI calls can not be nested") } startTx(ctx) out.WriteOK() return nil }
// LSET func (m *Miniredis) cmdLset(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] index, 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.WriteErrorString(msgKeyNotFound) return } if db.t(key) != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] if index < 0 { index = len(l) + index } if index < 0 || index > len(l)-1 { out.WriteErrorString(msgOutOfRange) return } l[index] = value db.keyVersion[key]++ out.WriteOK() }) }
// LTRIM func (m *Miniredis) cmdLtrim(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) t, ok := db.keys[key] if !ok { out.WriteOK() return } if t != "list" { out.WriteErrorString(msgWrongType) return } l := db.listKeys[key] rs, re := redisRange(len(l), start, end, false) db.listKeys[key] = l[rs:re] db.keyVersion[key]++ out.WriteOK() }) }
// SELECT func (m *Miniredis) cmdSelect(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) out.WriteErrorString("usage error") return nil } id, err := strconv.Atoi(r.Args[0]) if err != nil { id = 0 } m.Lock() defer m.Unlock() ctx := getCtx(r.Client()) ctx.selectedDB = id out.WriteOK() return nil }
// AUTH func (m *Redico) cmdAuth(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) != 1 { setDirty(r.Client()) return r.WrongNumberOfArgs() } pw := r.Args[0] m.Lock() defer m.Unlock() if m.password == "" { out.WriteErrorString("ERR Client sent AUTH, but no password is set") return nil } if m.password != pw { out.WriteErrorString("ERR invalid password") return nil } setAuthenticated(r.Client()) out.WriteOK() return nil }
// WATCH func (m *Miniredis) cmdWatch(out *redeo.Responder, r *redeo.Request) error { if len(r.Args) == 0 { setDirty(r.Client()) out.WriteErrorString("ERR wrong number of arguments for 'watch' command") return nil } ctx := getCtx(r.Client()) if inTx(ctx) { out.WriteErrorString("ERR WATCH in MULTI") return nil } m.Lock() defer m.Unlock() db := m.db(ctx.selectedDB) for _, key := range r.Args { watch(db, ctx, key) } out.WriteOK() return nil }
// SELECT func (m *Redico) cmdSelect(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 } id, err := strconv.Atoi(r.Args[0]) if err != nil { id = 0 } m.Lock() defer m.Unlock() ctx := getCtx(r.Client()) ctx.selectedDB = id out.WriteOK() return nil }
// RENAME func (m *Miniredis) cmdRename(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 } from := r.Args[0] to := r.Args[1] return withTx(m, out, r, func(out *redeo.Responder, ctx *connCtx) { db := m.db(ctx.selectedDB) if !db.exists(from) { out.WriteErrorString(msgKeyNotFound) return } db.rename(from, to) out.WriteOK() }) }
// WATCH func (m *Miniredis) cmdWatch(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("WATCH in MULTI") } m.Lock() defer m.Unlock() db := m.db(ctx.selectedDB) for _, key := range r.Args { watch(db, ctx, key) } out.WriteOK() return nil }
// AUTH func (m *Miniredis) cmdAuth(out *redeo.Responder, r *redeo.Request) error { out.WriteOK() return nil }
// QUIT func (m *Redico) cmdQuit(out *redeo.Responder, r *redeo.Request) error { // QUIT isn't transactionfied and accepts any arguments. out.WriteOK() r.Client().Close() return nil }
// SET func (m *Miniredis) cmdSet(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 } var ( nx = false // set iff not exists xx = false // set iff exists expire = 0 // For seconds and milliseconds. ) key := r.Args[0] value := r.Args[1] r.Args = r.Args[2:] for len(r.Args) > 0 { switch strings.ToUpper(r.Args[0]) { case "NX": nx = true r.Args = r.Args[1:] continue case "XX": xx = true r.Args = r.Args[1:] continue case "EX", "PX": if len(r.Args) < 2 { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } var err error expire, err = strconv.Atoi(r.Args[1]) if err != nil { setDirty(r.Client()) out.WriteErrorString(msgInvalidInt) return nil } r.Args = r.Args[2:] continue default: 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 nx { if db.exists(key) { out.WriteNil() return } } if xx { if !db.exists(key) { out.WriteNil() return } } db.del(key, true) // be sure to remove existing values of other type keys. // a vanilla SET clears the expire db.stringSet(key, value) if expire != 0 { db.expire[key] = expire } out.WriteOK() }) }