src.dualinventive.com/go/authentication-service/vendor/github.com/alicebob/miniredis/redis.go

209 lines
4.9 KiB
Go

package miniredis
import (
"fmt"
"math"
"strings"
"sync"
"time"
"github.com/alicebob/miniredis/server"
)
const (
msgWrongType = "WRONGTYPE Operation against a key holding the wrong kind of value"
msgInvalidInt = "ERR value is not an integer or out of range"
msgInvalidFloat = "ERR value is not a valid float"
msgInvalidMinMax = "ERR min or max is not a float"
msgInvalidRangeItem = "ERR min or max not valid string range item"
msgInvalidTimeout = "ERR timeout is not an integer or out of range"
msgSyntaxError = "ERR syntax error"
msgKeyNotFound = "ERR no such key"
msgOutOfRange = "ERR index out of range"
msgInvalidCursor = "ERR invalid cursor"
msgXXandNX = "ERR XX and NX options at the same time are not compatible"
msgNegTimeout = "ERR timeout is negative"
msgInvalidSETime = "ERR invalid expire time in set"
msgInvalidSETEXTime = "ERR invalid expire time in setex"
msgInvalidPSETEXTime = "ERR invalid expire time in psetex"
msgInvalidKeysNumber = "ERR Number of keys can't be greater than number of args"
msgNegativeKeysNumber = "ERR Number of keys can't be negative"
msgFScriptUsage = "ERR Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP."
msgSingleElementPair = "ERR INCR option supports a single increment-element pair"
msgNoScriptFound = "NOSCRIPT No matching script. Please use EVAL."
)
func errWrongNumber(cmd string) string {
return fmt.Sprintf("ERR wrong number of arguments for '%s' command", strings.ToLower(cmd))
}
func errLuaParseError(err error) string {
return fmt.Sprintf("ERR Error compiling script (new function): %s", err.Error())
}
// withTx wraps the non-argument-checking part of command handling code in
// transaction logic.
func withTx(
m *Miniredis,
c *server.Peer,
cb txCmd,
) {
ctx := getCtx(c)
if inTx(ctx) {
addTxCmd(ctx, cb)
c.WriteInline("QUEUED")
return
}
m.Lock()
cb(c, ctx)
// done, wake up anyone who waits on anything.
m.signal.Broadcast()
m.Unlock()
}
// blockCmd is executed returns whether it is done
type blockCmd func(*server.Peer, *connCtx) bool
// blocking keeps trying a command until the callback returns true. Calls
// onTimeout after the timeout (or when we call this in a transaction).
func blocking(
m *Miniredis,
c *server.Peer,
timeout time.Duration,
cb blockCmd,
onTimeout func(*server.Peer),
) {
var (
ctx = getCtx(c)
dl *time.Timer
dlc <-chan time.Time
)
if inTx(ctx) {
addTxCmd(ctx, func(c *server.Peer, ctx *connCtx) {
if !cb(c, ctx) {
onTimeout(c)
}
})
c.WriteInline("QUEUED")
return
}
if timeout != 0 {
dl = time.NewTimer(timeout)
defer dl.Stop()
dlc = dl.C
}
m.Lock()
defer m.Unlock()
for {
done := cb(c, ctx)
if done {
return
}
// there is no cond.WaitTimeout(), so hence the the goroutine to wait
// for a timeout
var (
wg sync.WaitGroup
wakeup = make(chan struct{}, 1)
)
wg.Add(1)
go func() {
m.signal.Wait()
wakeup <- struct{}{}
wg.Done()
}()
select {
case <-wakeup:
case <-dlc:
onTimeout(c)
m.signal.Broadcast() // to kill the wakeup go routine
wg.Wait()
return
}
wg.Wait()
}
}
// formatFloat formats a float the way redis does (sort-of)
func formatFloat(v float64) string {
// Format with %f and strip trailing 0s. This is the most like Redis does
// it :(
// .12 is the magic number where most output is the same as Redis.
if math.IsInf(v, +1) {
return "inf"
}
if math.IsInf(v, -1) {
return "-inf"
}
sv := fmt.Sprintf("%.12f", v)
for strings.Contains(sv, ".") {
if sv[len(sv)-1] != '0' {
break
}
// Remove trailing 0s.
sv = sv[:len(sv)-1]
// Ends with a '.'.
if sv[len(sv)-1] == '.' {
sv = sv[:len(sv)-1]
break
}
}
return sv
}
// redisRange gives Go offsets for something l long with start/end in
// Redis semantics. Both start and end can be negative.
// Used for string range and list range things.
// The results can be used as: v[start:end]
// Note that GETRANGE (on a string key) never returns an empty string when end
// is a large negative number.
func redisRange(l, start, end int, stringSymantics bool) (int, int) {
if start < 0 {
start = l + start
if start < 0 {
start = 0
}
}
if start > l {
start = l
}
if end < 0 {
end = l + end
if end < 0 {
end = -1
if stringSymantics {
end = 0
}
}
}
end++ // end argument is inclusive in Redis.
if end > l {
end = l
}
if end < start {
return 0, 0
}
return start, end
}
// matchKeys filters only matching keys.
// Will return an empty list on invalid match expression.
func matchKeys(keys []string, match string) []string {
re := patternRE(match)
if re == nil {
// Special case, the given pattern won't match anything / is
// invalid.
return nil
}
res := []string{}
for _, k := range keys {
if !re.MatchString(k) {
continue
}
res = append(res, k)
}
return res
}