731 lines
15 KiB
Go
731 lines
15 KiB
Go
package miniredis
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
errInvalidEntryID = errors.New("stream ID is invalid")
|
|
)
|
|
|
|
// exists also updates the lru
|
|
func (db *RedisDB) exists(k string) bool {
|
|
_, ok := db.keys[k]
|
|
if ok {
|
|
db.lru[k] = db.master.effectiveNow()
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// t gives the type of a key, or ""
|
|
func (db *RedisDB) t(k string) string {
|
|
return db.keys[k]
|
|
}
|
|
|
|
// incr increases the version and the lru timestamp
|
|
func (db *RedisDB) incr(k string) {
|
|
db.lru[k] = db.master.effectiveNow()
|
|
db.keyVersion[k]++
|
|
}
|
|
|
|
// allKeys returns all keys. Sorted.
|
|
func (db *RedisDB) allKeys() []string {
|
|
res := make([]string, 0, len(db.keys))
|
|
for k := range db.keys {
|
|
res = append(res, k)
|
|
}
|
|
sort.Strings(res) // To make things deterministic.
|
|
return res
|
|
}
|
|
|
|
// flush removes all keys and values.
|
|
func (db *RedisDB) flush() {
|
|
db.keys = map[string]string{}
|
|
db.lru = map[string]time.Time{}
|
|
db.stringKeys = map[string]string{}
|
|
db.hashKeys = map[string]hashKey{}
|
|
db.listKeys = map[string]listKey{}
|
|
db.setKeys = map[string]setKey{}
|
|
db.hllKeys = map[string]*hll{}
|
|
db.sortedsetKeys = map[string]sortedSet{}
|
|
db.ttl = map[string]time.Duration{}
|
|
db.streamKeys = map[string]*streamKey{}
|
|
}
|
|
|
|
// move something to another db. Will return ok. Or not.
|
|
func (db *RedisDB) move(key string, to *RedisDB) bool {
|
|
if _, ok := to.keys[key]; ok {
|
|
return false
|
|
}
|
|
|
|
t, ok := db.keys[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
to.keys[key] = db.keys[key]
|
|
switch t {
|
|
case "string":
|
|
to.stringKeys[key] = db.stringKeys[key]
|
|
case "hash":
|
|
to.hashKeys[key] = db.hashKeys[key]
|
|
case "list":
|
|
to.listKeys[key] = db.listKeys[key]
|
|
case "set":
|
|
to.setKeys[key] = db.setKeys[key]
|
|
case "zset":
|
|
to.sortedsetKeys[key] = db.sortedsetKeys[key]
|
|
case "stream":
|
|
to.streamKeys[key] = db.streamKeys[key]
|
|
case "hll":
|
|
to.hllKeys[key] = db.hllKeys[key]
|
|
default:
|
|
panic("unhandled key type")
|
|
}
|
|
if v, ok := db.ttl[key]; ok {
|
|
to.ttl[key] = v
|
|
}
|
|
to.incr(key)
|
|
db.del(key, true)
|
|
return true
|
|
}
|
|
|
|
func (db *RedisDB) rename(from, to string) {
|
|
db.del(to, true)
|
|
switch db.t(from) {
|
|
case "string":
|
|
db.stringKeys[to] = db.stringKeys[from]
|
|
case "hash":
|
|
db.hashKeys[to] = db.hashKeys[from]
|
|
case "list":
|
|
db.listKeys[to] = db.listKeys[from]
|
|
case "set":
|
|
db.setKeys[to] = db.setKeys[from]
|
|
case "zset":
|
|
db.sortedsetKeys[to] = db.sortedsetKeys[from]
|
|
case "stream":
|
|
db.streamKeys[to] = db.streamKeys[from]
|
|
case "hll":
|
|
db.hllKeys[to] = db.hllKeys[from]
|
|
default:
|
|
panic("missing case")
|
|
}
|
|
db.keys[to] = db.keys[from]
|
|
if v, ok := db.ttl[from]; ok {
|
|
db.ttl[to] = v
|
|
}
|
|
db.incr(to)
|
|
|
|
db.del(from, true)
|
|
}
|
|
|
|
func (db *RedisDB) del(k string, delTTL bool) {
|
|
if !db.exists(k) {
|
|
return
|
|
}
|
|
t := db.t(k)
|
|
delete(db.keys, k)
|
|
delete(db.lru, k)
|
|
db.keyVersion[k]++
|
|
if delTTL {
|
|
delete(db.ttl, k)
|
|
}
|
|
switch t {
|
|
case "string":
|
|
delete(db.stringKeys, k)
|
|
case "hash":
|
|
delete(db.hashKeys, k)
|
|
case "list":
|
|
delete(db.listKeys, k)
|
|
case "set":
|
|
delete(db.setKeys, k)
|
|
case "zset":
|
|
delete(db.sortedsetKeys, k)
|
|
case "stream":
|
|
delete(db.streamKeys, k)
|
|
case "hll":
|
|
delete(db.hllKeys, k)
|
|
default:
|
|
panic("Unknown key type: " + t)
|
|
}
|
|
}
|
|
|
|
// stringGet returns the string key or "" on error/nonexists.
|
|
func (db *RedisDB) stringGet(k string) string {
|
|
if t, ok := db.keys[k]; !ok || t != "string" {
|
|
return ""
|
|
}
|
|
return db.stringKeys[k]
|
|
}
|
|
|
|
// stringSet force set()s a key. Does not touch expire.
|
|
func (db *RedisDB) stringSet(k, v string) {
|
|
db.del(k, false)
|
|
db.keys[k] = "string"
|
|
db.stringKeys[k] = v
|
|
db.incr(k)
|
|
}
|
|
|
|
// change int key value
|
|
func (db *RedisDB) stringIncr(k string, delta int) (int, error) {
|
|
v := 0
|
|
if sv, ok := db.stringKeys[k]; ok {
|
|
var err error
|
|
v, err = strconv.Atoi(sv)
|
|
if err != nil {
|
|
return 0, ErrIntValueError
|
|
}
|
|
}
|
|
v += delta
|
|
db.stringSet(k, strconv.Itoa(v))
|
|
return v, nil
|
|
}
|
|
|
|
// change float key value
|
|
func (db *RedisDB) stringIncrfloat(k string, delta *big.Float) (*big.Float, error) {
|
|
v := big.NewFloat(0.0)
|
|
v.SetPrec(128)
|
|
if sv, ok := db.stringKeys[k]; ok {
|
|
var err error
|
|
v, _, err = big.ParseFloat(sv, 10, 128, 0)
|
|
if err != nil {
|
|
return nil, ErrFloatValueError
|
|
}
|
|
}
|
|
v.Add(v, delta)
|
|
db.stringSet(k, formatBig(v))
|
|
return v, nil
|
|
}
|
|
|
|
// listLpush is 'left push', aka unshift. Returns the new length.
|
|
func (db *RedisDB) listLpush(k, v string) int {
|
|
l, ok := db.listKeys[k]
|
|
if !ok {
|
|
db.keys[k] = "list"
|
|
}
|
|
l = append([]string{v}, l...)
|
|
db.listKeys[k] = l
|
|
db.incr(k)
|
|
return len(l)
|
|
}
|
|
|
|
// 'left pop', aka shift.
|
|
func (db *RedisDB) listLpop(k string) string {
|
|
l := db.listKeys[k]
|
|
el := l[0]
|
|
l = l[1:]
|
|
if len(l) == 0 {
|
|
db.del(k, true)
|
|
} else {
|
|
db.listKeys[k] = l
|
|
}
|
|
db.incr(k)
|
|
return el
|
|
}
|
|
|
|
func (db *RedisDB) listPush(k string, v ...string) int {
|
|
l, ok := db.listKeys[k]
|
|
if !ok {
|
|
db.keys[k] = "list"
|
|
}
|
|
l = append(l, v...)
|
|
db.listKeys[k] = l
|
|
db.incr(k)
|
|
return len(l)
|
|
}
|
|
|
|
func (db *RedisDB) listPop(k string) string {
|
|
l := db.listKeys[k]
|
|
el := l[len(l)-1]
|
|
l = l[:len(l)-1]
|
|
if len(l) == 0 {
|
|
db.del(k, true)
|
|
} else {
|
|
db.listKeys[k] = l
|
|
db.incr(k)
|
|
}
|
|
return el
|
|
}
|
|
|
|
// setset replaces a whole set.
|
|
func (db *RedisDB) setSet(k string, set setKey) {
|
|
db.keys[k] = "set"
|
|
db.setKeys[k] = set
|
|
db.incr(k)
|
|
}
|
|
|
|
// setadd adds members to a set. Returns nr of new keys.
|
|
func (db *RedisDB) setAdd(k string, elems ...string) int {
|
|
s, ok := db.setKeys[k]
|
|
if !ok {
|
|
s = setKey{}
|
|
db.keys[k] = "set"
|
|
}
|
|
added := 0
|
|
for _, e := range elems {
|
|
if _, ok := s[e]; !ok {
|
|
added++
|
|
}
|
|
s[e] = struct{}{}
|
|
}
|
|
db.setKeys[k] = s
|
|
db.incr(k)
|
|
return added
|
|
}
|
|
|
|
// setrem removes members from a set. Returns nr of deleted keys.
|
|
func (db *RedisDB) setRem(k string, fields ...string) int {
|
|
s, ok := db.setKeys[k]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
removed := 0
|
|
for _, f := range fields {
|
|
if _, ok := s[f]; ok {
|
|
removed++
|
|
delete(s, f)
|
|
}
|
|
}
|
|
if len(s) == 0 {
|
|
db.del(k, true)
|
|
} else {
|
|
db.setKeys[k] = s
|
|
}
|
|
db.incr(k)
|
|
return removed
|
|
}
|
|
|
|
// All members of a set.
|
|
func (db *RedisDB) setMembers(k string) []string {
|
|
set := db.setKeys[k]
|
|
members := make([]string, 0, len(set))
|
|
for k := range set {
|
|
members = append(members, k)
|
|
}
|
|
sort.Strings(members)
|
|
return members
|
|
}
|
|
|
|
// Is a SET value present?
|
|
func (db *RedisDB) setIsMember(k, v string) bool {
|
|
set, ok := db.setKeys[k]
|
|
if !ok {
|
|
return false
|
|
}
|
|
_, ok = set[v]
|
|
return ok
|
|
}
|
|
|
|
// hashFields returns all (sorted) keys ('fields') for a hash key.
|
|
func (db *RedisDB) hashFields(k string) []string {
|
|
v := db.hashKeys[k]
|
|
var r []string
|
|
for k := range v {
|
|
r = append(r, k)
|
|
}
|
|
sort.Strings(r)
|
|
return r
|
|
}
|
|
|
|
// hashValues returns all (sorted) values a hash key.
|
|
func (db *RedisDB) hashValues(k string) []string {
|
|
h := db.hashKeys[k]
|
|
var r []string
|
|
for _, v := range h {
|
|
r = append(r, v)
|
|
}
|
|
sort.Strings(r)
|
|
return r
|
|
}
|
|
|
|
// hashGet a value
|
|
func (db *RedisDB) hashGet(key, field string) string {
|
|
return db.hashKeys[key][field]
|
|
}
|
|
|
|
// hashSet returns the number of new keys
|
|
func (db *RedisDB) hashSet(k string, fv ...string) int {
|
|
if t, ok := db.keys[k]; ok && t != "hash" {
|
|
db.del(k, true)
|
|
}
|
|
db.keys[k] = "hash"
|
|
if _, ok := db.hashKeys[k]; !ok {
|
|
db.hashKeys[k] = map[string]string{}
|
|
}
|
|
new := 0
|
|
for idx := 0; idx < len(fv)-1; idx = idx + 2 {
|
|
f, v := fv[idx], fv[idx+1]
|
|
_, ok := db.hashKeys[k][f]
|
|
db.hashKeys[k][f] = v
|
|
db.incr(k)
|
|
if !ok {
|
|
new++
|
|
}
|
|
}
|
|
return new
|
|
}
|
|
|
|
// hashIncr changes int key value
|
|
func (db *RedisDB) hashIncr(key, field string, delta int) (int, error) {
|
|
v := 0
|
|
if h, ok := db.hashKeys[key]; ok {
|
|
if f, ok := h[field]; ok {
|
|
var err error
|
|
v, err = strconv.Atoi(f)
|
|
if err != nil {
|
|
return 0, ErrIntValueError
|
|
}
|
|
}
|
|
}
|
|
v += delta
|
|
db.hashSet(key, field, strconv.Itoa(v))
|
|
return v, nil
|
|
}
|
|
|
|
// hashIncrfloat changes float key value
|
|
func (db *RedisDB) hashIncrfloat(key, field string, delta *big.Float) (*big.Float, error) {
|
|
v := big.NewFloat(0.0)
|
|
v.SetPrec(128)
|
|
if h, ok := db.hashKeys[key]; ok {
|
|
if f, ok := h[field]; ok {
|
|
var err error
|
|
v, _, err = big.ParseFloat(f, 10, 128, 0)
|
|
if err != nil {
|
|
return nil, ErrFloatValueError
|
|
}
|
|
}
|
|
}
|
|
v.Add(v, delta)
|
|
db.hashSet(key, field, formatBig(v))
|
|
return v, nil
|
|
}
|
|
|
|
// sortedSet set returns a sortedSet as map
|
|
func (db *RedisDB) sortedSet(key string) map[string]float64 {
|
|
ss := db.sortedsetKeys[key]
|
|
return map[string]float64(ss)
|
|
}
|
|
|
|
// ssetSet sets a complete sorted set.
|
|
func (db *RedisDB) ssetSet(key string, sset sortedSet) {
|
|
db.keys[key] = "zset"
|
|
db.incr(key)
|
|
db.sortedsetKeys[key] = sset
|
|
}
|
|
|
|
// ssetAdd adds member to a sorted set. Returns whether this was a new member.
|
|
func (db *RedisDB) ssetAdd(key string, score float64, member string) bool {
|
|
ss, ok := db.sortedsetKeys[key]
|
|
if !ok {
|
|
ss = newSortedSet()
|
|
db.keys[key] = "zset"
|
|
}
|
|
_, ok = ss[member]
|
|
ss[member] = score
|
|
db.sortedsetKeys[key] = ss
|
|
db.incr(key)
|
|
return !ok
|
|
}
|
|
|
|
// All members from a sorted set, ordered by score.
|
|
func (db *RedisDB) ssetMembers(key string) []string {
|
|
ss, ok := db.sortedsetKeys[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
elems := ss.byScore(asc)
|
|
members := make([]string, 0, len(elems))
|
|
for _, e := range elems {
|
|
members = append(members, e.member)
|
|
}
|
|
return members
|
|
}
|
|
|
|
// All members+scores from a sorted set, ordered by score.
|
|
func (db *RedisDB) ssetElements(key string) ssElems {
|
|
ss, ok := db.sortedsetKeys[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return ss.byScore(asc)
|
|
}
|
|
|
|
func (db *RedisDB) ssetRandomMember(key string) string {
|
|
elems := db.ssetElements(key)
|
|
if len(elems) == 0 {
|
|
return ""
|
|
}
|
|
return elems[db.master.randIntn(len(elems))].member
|
|
}
|
|
|
|
// ssetCard is the sorted set cardinality.
|
|
func (db *RedisDB) ssetCard(key string) int {
|
|
ss := db.sortedsetKeys[key]
|
|
return ss.card()
|
|
}
|
|
|
|
// ssetRank is the sorted set rank.
|
|
func (db *RedisDB) ssetRank(key, member string, d direction) (int, bool) {
|
|
ss := db.sortedsetKeys[key]
|
|
return ss.rankByScore(member, d)
|
|
}
|
|
|
|
// ssetScore is sorted set score.
|
|
func (db *RedisDB) ssetScore(key, member string) float64 {
|
|
ss := db.sortedsetKeys[key]
|
|
return ss[member]
|
|
}
|
|
|
|
// ssetMScore returns multiple scores of a list of members in a sorted set.
|
|
func (db *RedisDB) ssetMScore(key string, members []string) []float64 {
|
|
scores := make([]float64, 0, len(members))
|
|
ss := db.sortedsetKeys[key]
|
|
for _, member := range members {
|
|
scores = append(scores, ss[member])
|
|
}
|
|
return scores
|
|
}
|
|
|
|
// ssetRem is sorted set key delete.
|
|
func (db *RedisDB) ssetRem(key, member string) bool {
|
|
ss := db.sortedsetKeys[key]
|
|
_, ok := ss[member]
|
|
delete(ss, member)
|
|
if len(ss) == 0 {
|
|
// Delete key on removal of last member
|
|
db.del(key, true)
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// ssetExists tells if a member exists in a sorted set.
|
|
func (db *RedisDB) ssetExists(key, member string) bool {
|
|
ss := db.sortedsetKeys[key]
|
|
_, ok := ss[member]
|
|
return ok
|
|
}
|
|
|
|
// ssetIncrby changes float sorted set score.
|
|
func (db *RedisDB) ssetIncrby(k, m string, delta float64) float64 {
|
|
ss, ok := db.sortedsetKeys[k]
|
|
if !ok {
|
|
ss = newSortedSet()
|
|
db.keys[k] = "zset"
|
|
db.sortedsetKeys[k] = ss
|
|
}
|
|
|
|
v, _ := ss.get(m)
|
|
v += delta
|
|
ss.set(v, m)
|
|
db.incr(k)
|
|
return v
|
|
}
|
|
|
|
// setDiff implements the logic behind SDIFF*
|
|
func (db *RedisDB) setDiff(keys []string) (setKey, error) {
|
|
key := keys[0]
|
|
keys = keys[1:]
|
|
if db.exists(key) && db.t(key) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
s := setKey{}
|
|
for k := range db.setKeys[key] {
|
|
s[k] = struct{}{}
|
|
}
|
|
for _, sk := range keys {
|
|
if !db.exists(sk) {
|
|
continue
|
|
}
|
|
if db.t(sk) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
for e := range db.setKeys[sk] {
|
|
delete(s, e)
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// setInter implements the logic behind SINTER*
|
|
// len keys needs to be > 0
|
|
func (db *RedisDB) setInter(keys []string) (setKey, error) {
|
|
// all keys must either not exist, or be of type "set".
|
|
for _, key := range keys {
|
|
if db.exists(key) && db.t(key) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
}
|
|
|
|
key := keys[0]
|
|
keys = keys[1:]
|
|
if !db.exists(key) {
|
|
return nil, nil
|
|
}
|
|
if db.t(key) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
s := setKey{}
|
|
for k := range db.setKeys[key] {
|
|
s[k] = struct{}{}
|
|
}
|
|
for _, sk := range keys {
|
|
if !db.exists(sk) {
|
|
return setKey{}, nil
|
|
}
|
|
if db.t(sk) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
other := db.setKeys[sk]
|
|
for e := range s {
|
|
if _, ok := other[e]; ok {
|
|
continue
|
|
}
|
|
delete(s, e)
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// setUnion implements the logic behind SUNION*
|
|
func (db *RedisDB) setUnion(keys []string) (setKey, error) {
|
|
key := keys[0]
|
|
keys = keys[1:]
|
|
if db.exists(key) && db.t(key) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
s := setKey{}
|
|
for k := range db.setKeys[key] {
|
|
s[k] = struct{}{}
|
|
}
|
|
for _, sk := range keys {
|
|
if !db.exists(sk) {
|
|
continue
|
|
}
|
|
if db.t(sk) != "set" {
|
|
return nil, ErrWrongType
|
|
}
|
|
for e := range db.setKeys[sk] {
|
|
s[e] = struct{}{}
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (db *RedisDB) newStream(key string) (*streamKey, error) {
|
|
if s, err := db.stream(key); err != nil {
|
|
return nil, err
|
|
} else if s != nil {
|
|
return nil, fmt.Errorf("ErrAlreadyExists")
|
|
}
|
|
|
|
db.keys[key] = "stream"
|
|
s := newStreamKey()
|
|
db.streamKeys[key] = s
|
|
db.incr(key)
|
|
return s, nil
|
|
}
|
|
|
|
// return existing stream, or nil.
|
|
func (db *RedisDB) stream(key string) (*streamKey, error) {
|
|
if db.exists(key) && db.t(key) != "stream" {
|
|
return nil, ErrWrongType
|
|
}
|
|
|
|
return db.streamKeys[key], nil
|
|
}
|
|
|
|
// return existing stream group, or nil.
|
|
func (db *RedisDB) streamGroup(key, group string) (*streamGroup, error) {
|
|
s, err := db.stream(key)
|
|
if err != nil || s == nil {
|
|
return nil, err
|
|
}
|
|
return s.groups[group], nil
|
|
}
|
|
|
|
// fastForward proceeds the current timestamp with duration, works as a time machine
|
|
func (db *RedisDB) fastForward(duration time.Duration) {
|
|
for _, key := range db.allKeys() {
|
|
if value, ok := db.ttl[key]; ok {
|
|
db.ttl[key] = value - duration
|
|
db.checkTTL(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (db *RedisDB) checkTTL(key string) {
|
|
if v, ok := db.ttl[key]; ok && v <= 0 {
|
|
db.del(key, true)
|
|
}
|
|
}
|
|
|
|
// hllAdd adds members to a hll. Returns 1 if at least 1 if internal HyperLogLog was altered, otherwise 0
|
|
func (db *RedisDB) hllAdd(k string, elems ...string) int {
|
|
s, ok := db.hllKeys[k]
|
|
if !ok {
|
|
s = newHll()
|
|
db.keys[k] = "hll"
|
|
}
|
|
hllAltered := 0
|
|
for _, e := range elems {
|
|
if s.Add([]byte(e)) {
|
|
hllAltered = 1
|
|
}
|
|
}
|
|
db.hllKeys[k] = s
|
|
db.incr(k)
|
|
return hllAltered
|
|
}
|
|
|
|
// hllCount estimates the amount of members added to hll by hllAdd. If called with several arguments, hllCount returns a sum of estimations
|
|
func (db *RedisDB) hllCount(keys []string) (int, error) {
|
|
countOverall := 0
|
|
for _, key := range keys {
|
|
if db.exists(key) && db.t(key) != "hll" {
|
|
return 0, ErrNotValidHllValue
|
|
}
|
|
if !db.exists(key) {
|
|
continue
|
|
}
|
|
countOverall += db.hllKeys[key].Count()
|
|
}
|
|
|
|
return countOverall, nil
|
|
}
|
|
|
|
// hllMerge merges all the hlls provided as keys to the first key. Creates a new hll in the first key if it contains nothing
|
|
func (db *RedisDB) hllMerge(keys []string) error {
|
|
for _, key := range keys {
|
|
if db.exists(key) && db.t(key) != "hll" {
|
|
return ErrNotValidHllValue
|
|
}
|
|
}
|
|
|
|
destKey := keys[0]
|
|
restKeys := keys[1:]
|
|
|
|
var destHll *hll
|
|
if db.exists(destKey) {
|
|
destHll = db.hllKeys[destKey]
|
|
} else {
|
|
destHll = newHll()
|
|
}
|
|
|
|
for _, key := range restKeys {
|
|
if !db.exists(key) {
|
|
continue
|
|
}
|
|
destHll.Merge(db.hllKeys[key])
|
|
}
|
|
|
|
db.hllKeys[destKey] = destHll
|
|
db.keys[destKey] = "hll"
|
|
db.incr(destKey)
|
|
|
|
return nil
|
|
}
|