Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions cmd_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,28 @@ func inMilliSeconds(t time.Time) int {
func commandsGeneric(m *Miniredis) {
m.srv.Register("COPY", m.cmdCopy)
m.srv.Register("DEL", m.cmdDel)
m.srv.Register("DUMP", m.cmdDump)
m.srv.Register("EXISTS", m.cmdExists)
m.srv.Register("DUMP", m.cmdDump, server.ReadOnlyOption())
m.srv.Register("EXISTS", m.cmdExists, server.ReadOnlyOption())
m.srv.Register("EXPIRE", makeCmdExpire(m, false, time.Second))
m.srv.Register("EXPIREAT", makeCmdExpire(m, true, time.Second))
m.srv.Register("EXPIRETIME", m.makeCmdExpireTime(inSeconds))
m.srv.Register("PEXPIRETIME", m.makeCmdExpireTime(inMilliSeconds))
m.srv.Register("KEYS", m.cmdKeys)
m.srv.Register("EXPIRETIME", m.makeCmdExpireTime(inSeconds), server.ReadOnlyOption())
m.srv.Register("PEXPIRETIME", m.makeCmdExpireTime(inMilliSeconds), server.ReadOnlyOption())
m.srv.Register("KEYS", m.cmdKeys, server.ReadOnlyOption())
// MIGRATE
m.srv.Register("MOVE", m.cmdMove)
// OBJECT
m.srv.Register("PERSIST", m.cmdPersist)
m.srv.Register("PEXPIRE", makeCmdExpire(m, false, time.Millisecond))
m.srv.Register("PEXPIREAT", makeCmdExpire(m, true, time.Millisecond))
m.srv.Register("PTTL", m.cmdPTTL)
m.srv.Register("RANDOMKEY", m.cmdRandomkey)
m.srv.Register("PTTL", m.cmdPTTL, server.ReadOnlyOption())
m.srv.Register("RANDOMKEY", m.cmdRandomkey, server.ReadOnlyOption())
m.srv.Register("RENAME", m.cmdRename)
m.srv.Register("RENAMENX", m.cmdRenamenx)
m.srv.Register("RESTORE", m.cmdRestore)
m.srv.Register("TOUCH", m.cmdTouch)
m.srv.Register("TTL", m.cmdTTL)
m.srv.Register("TYPE", m.cmdType)
m.srv.Register("SCAN", m.cmdScan)
m.srv.Register("TOUCH", m.cmdTouch, server.ReadOnlyOption())
m.srv.Register("TTL", m.cmdTTL, server.ReadOnlyOption())
m.srv.Register("TYPE", m.cmdType, server.ReadOnlyOption())
m.srv.Register("SCAN", m.cmdScan, server.ReadOnlyOption())
// SORT
m.srv.Register("UNLINK", m.cmdDel)
}
Expand Down
8 changes: 4 additions & 4 deletions cmd_geo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import (
// commandsGeo handles GEOADD, GEORADIUS etc.
func commandsGeo(m *Miniredis) {
m.srv.Register("GEOADD", m.cmdGeoadd)
m.srv.Register("GEODIST", m.cmdGeodist)
m.srv.Register("GEOPOS", m.cmdGeopos)
m.srv.Register("GEODIST", m.cmdGeodist, server.ReadOnlyOption())
m.srv.Register("GEOPOS", m.cmdGeopos, server.ReadOnlyOption())
m.srv.Register("GEORADIUS", m.cmdGeoradius)
m.srv.Register("GEORADIUS_RO", m.cmdGeoradius)
m.srv.Register("GEORADIUS_RO", m.cmdGeoradius, server.ReadOnlyOption())
m.srv.Register("GEORADIUSBYMEMBER", m.cmdGeoradiusbymember)
m.srv.Register("GEORADIUSBYMEMBER_RO", m.cmdGeoradiusbymember)
m.srv.Register("GEORADIUSBYMEMBER_RO", m.cmdGeoradiusbymember, server.ReadOnlyOption())
}

// GEOADD
Expand Down
20 changes: 10 additions & 10 deletions cmd_hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import (
// commandsHash handles all hash value operations.
func commandsHash(m *Miniredis) {
m.srv.Register("HDEL", m.cmdHdel)
m.srv.Register("HEXISTS", m.cmdHexists)
m.srv.Register("HGET", m.cmdHget)
m.srv.Register("HGETALL", m.cmdHgetall)
m.srv.Register("HEXISTS", m.cmdHexists, server.ReadOnlyOption())
m.srv.Register("HGET", m.cmdHget, server.ReadOnlyOption())
m.srv.Register("HGETALL", m.cmdHgetall, server.ReadOnlyOption())
m.srv.Register("HINCRBY", m.cmdHincrby)
m.srv.Register("HINCRBYFLOAT", m.cmdHincrbyfloat)
m.srv.Register("HKEYS", m.cmdHkeys)
m.srv.Register("HLEN", m.cmdHlen)
m.srv.Register("HMGET", m.cmdHmget)
m.srv.Register("HKEYS", m.cmdHkeys, server.ReadOnlyOption())
m.srv.Register("HLEN", m.cmdHlen, server.ReadOnlyOption())
m.srv.Register("HMGET", m.cmdHmget, server.ReadOnlyOption())
m.srv.Register("HMSET", m.cmdHmset)
m.srv.Register("HSET", m.cmdHset)
m.srv.Register("HSETNX", m.cmdHsetnx)
m.srv.Register("HSTRLEN", m.cmdHstrlen)
m.srv.Register("HVALS", m.cmdHvals)
m.srv.Register("HSCAN", m.cmdHscan)
m.srv.Register("HRANDFIELD", m.cmdHrandfield)
m.srv.Register("HSTRLEN", m.cmdHstrlen, server.ReadOnlyOption())
m.srv.Register("HVALS", m.cmdHvals, server.ReadOnlyOption())
m.srv.Register("HSCAN", m.cmdHscan, server.ReadOnlyOption())
m.srv.Register("HRANDFIELD", m.cmdHrandfield, server.ReadOnlyOption())
}

// HSET
Expand Down
2 changes: 1 addition & 1 deletion cmd_hll.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "github.com/alicebob/miniredis/v2/server"
// commandsHll handles all hll related operations.
func commandsHll(m *Miniredis) {
m.srv.Register("PFADD", m.cmdPfadd)
m.srv.Register("PFCOUNT", m.cmdPfcount)
m.srv.Register("PFCOUNT", m.cmdPfcount, server.ReadOnlyOption())
m.srv.Register("PFMERGE", m.cmdPfmerge)
}

Expand Down
8 changes: 4 additions & 4 deletions cmd_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ func commandsList(m *Miniredis) {
m.srv.Register("BLPOP", m.cmdBlpop)
m.srv.Register("BRPOP", m.cmdBrpop)
m.srv.Register("BRPOPLPUSH", m.cmdBrpoplpush)
m.srv.Register("LINDEX", m.cmdLindex)
m.srv.Register("LPOS", m.cmdLpos)
m.srv.Register("LINDEX", m.cmdLindex, server.ReadOnlyOption())
m.srv.Register("LPOS", m.cmdLpos, server.ReadOnlyOption())
m.srv.Register("LINSERT", m.cmdLinsert)
m.srv.Register("LLEN", m.cmdLlen)
m.srv.Register("LLEN", m.cmdLlen, server.ReadOnlyOption())
m.srv.Register("LPOP", m.cmdLpop)
m.srv.Register("LPUSH", m.cmdLpush)
m.srv.Register("LPUSHX", m.cmdLpushx)
m.srv.Register("LRANGE", m.cmdLrange)
m.srv.Register("LRANGE", m.cmdLrange, server.ReadOnlyOption())
m.srv.Register("LREM", m.cmdLrem)
m.srv.Register("LSET", m.cmdLset)
m.srv.Register("LTRIM", m.cmdLtrim)
Expand Down
2 changes: 1 addition & 1 deletion cmd_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func commandsServer(m *Miniredis) {
m.srv.Register("COMMAND", m.cmdCommand)
m.srv.Register("DBSIZE", m.cmdDbsize)
m.srv.Register("DBSIZE", m.cmdDbsize, server.ReadOnlyOption())
m.srv.Register("FLUSHALL", m.cmdFlushall)
m.srv.Register("FLUSHDB", m.cmdFlushdb)
m.srv.Register("INFO", m.cmdInfo)
Expand Down
20 changes: 10 additions & 10 deletions cmd_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ import (
// commandsSet handles all set value operations.
func commandsSet(m *Miniredis) {
m.srv.Register("SADD", m.cmdSadd)
m.srv.Register("SCARD", m.cmdScard)
m.srv.Register("SDIFF", m.cmdSdiff)
m.srv.Register("SCARD", m.cmdScard, server.ReadOnlyOption())
m.srv.Register("SDIFF", m.cmdSdiff, server.ReadOnlyOption())
m.srv.Register("SDIFFSTORE", m.cmdSdiffstore)
m.srv.Register("SINTERCARD", m.cmdSintercard)
m.srv.Register("SINTER", m.cmdSinter)
m.srv.Register("SINTERCARD", m.cmdSintercard, server.ReadOnlyOption())
m.srv.Register("SINTER", m.cmdSinter, server.ReadOnlyOption())
m.srv.Register("SINTERSTORE", m.cmdSinterstore)
m.srv.Register("SISMEMBER", m.cmdSismember)
m.srv.Register("SMEMBERS", m.cmdSmembers)
m.srv.Register("SMISMEMBER", m.cmdSmismember)
m.srv.Register("SISMEMBER", m.cmdSismember, server.ReadOnlyOption())
m.srv.Register("SMEMBERS", m.cmdSmembers, server.ReadOnlyOption())
m.srv.Register("SMISMEMBER", m.cmdSmismember, server.ReadOnlyOption())
m.srv.Register("SMOVE", m.cmdSmove)
m.srv.Register("SPOP", m.cmdSpop)
m.srv.Register("SRANDMEMBER", m.cmdSrandmember)
m.srv.Register("SRANDMEMBER", m.cmdSrandmember, server.ReadOnlyOption())
m.srv.Register("SREM", m.cmdSrem)
m.srv.Register("SUNION", m.cmdSunion)
m.srv.Register("SUNION", m.cmdSunion, server.ReadOnlyOption())
m.srv.Register("SUNIONSTORE", m.cmdSunionstore)
m.srv.Register("SSCAN", m.cmdSscan)
m.srv.Register("SSCAN", m.cmdSscan, server.ReadOnlyOption())
}

// SADD
Expand Down
34 changes: 17 additions & 17 deletions cmd_sorted_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@ import (
// commandsSortedSet handles all sorted set operations.
func commandsSortedSet(m *Miniredis) {
m.srv.Register("ZADD", m.cmdZadd)
m.srv.Register("ZCARD", m.cmdZcard)
m.srv.Register("ZCOUNT", m.cmdZcount)
m.srv.Register("ZCARD", m.cmdZcard, server.ReadOnlyOption())
m.srv.Register("ZCOUNT", m.cmdZcount, server.ReadOnlyOption())
m.srv.Register("ZINCRBY", m.cmdZincrby)
m.srv.Register("ZINTER", m.makeCmdZinter(false))
m.srv.Register("ZINTER", m.makeCmdZinter(false), server.ReadOnlyOption())
m.srv.Register("ZINTERSTORE", m.makeCmdZinter(true))
m.srv.Register("ZLEXCOUNT", m.cmdZlexcount)
m.srv.Register("ZRANGE", m.cmdZrange)
m.srv.Register("ZRANGEBYLEX", m.makeCmdZrangebylex(false))
m.srv.Register("ZRANGEBYSCORE", m.makeCmdZrangebyscore(false))
m.srv.Register("ZRANK", m.makeCmdZrank(false))
m.srv.Register("ZLEXCOUNT", m.cmdZlexcount, server.ReadOnlyOption())
m.srv.Register("ZRANGE", m.cmdZrange, server.ReadOnlyOption())
m.srv.Register("ZRANGEBYLEX", m.makeCmdZrangebylex(false), server.ReadOnlyOption())
m.srv.Register("ZRANGEBYSCORE", m.makeCmdZrangebyscore(false), server.ReadOnlyOption())
m.srv.Register("ZRANK", m.makeCmdZrank(false), server.ReadOnlyOption())
m.srv.Register("ZREM", m.cmdZrem)
m.srv.Register("ZREMRANGEBYLEX", m.cmdZremrangebylex)
m.srv.Register("ZREMRANGEBYRANK", m.cmdZremrangebyrank)
m.srv.Register("ZREMRANGEBYSCORE", m.cmdZremrangebyscore)
m.srv.Register("ZREVRANGE", m.cmdZrevrange)
m.srv.Register("ZREVRANGEBYLEX", m.makeCmdZrangebylex(true))
m.srv.Register("ZREVRANGEBYSCORE", m.makeCmdZrangebyscore(true))
m.srv.Register("ZREVRANK", m.makeCmdZrank(true))
m.srv.Register("ZSCORE", m.cmdZscore)
m.srv.Register("ZMSCORE", m.cmdZMscore)
m.srv.Register("ZUNION", m.cmdZunion)
m.srv.Register("ZREVRANGE", m.cmdZrevrange, server.ReadOnlyOption())
m.srv.Register("ZREVRANGEBYLEX", m.makeCmdZrangebylex(true), server.ReadOnlyOption())
m.srv.Register("ZREVRANGEBYSCORE", m.makeCmdZrangebyscore(true), server.ReadOnlyOption())
m.srv.Register("ZREVRANK", m.makeCmdZrank(true), server.ReadOnlyOption())
m.srv.Register("ZSCORE", m.cmdZscore, server.ReadOnlyOption())
m.srv.Register("ZMSCORE", m.cmdZMscore, server.ReadOnlyOption())
m.srv.Register("ZUNION", m.cmdZunion, server.ReadOnlyOption())
m.srv.Register("ZUNIONSTORE", m.cmdZunionstore)
m.srv.Register("ZSCAN", m.cmdZscan)
m.srv.Register("ZSCAN", m.cmdZscan, server.ReadOnlyOption())
m.srv.Register("ZPOPMAX", m.cmdZpopmax(true))
m.srv.Register("ZPOPMIN", m.cmdZpopmax(false))
m.srv.Register("ZRANDMEMBER", m.cmdZrandmember)
m.srv.Register("ZRANDMEMBER", m.cmdZrandmember, server.ReadOnlyOption())
}

// ZADD
Expand Down
10 changes: 5 additions & 5 deletions cmd_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ import (
// commandsStream handles all stream operations.
func commandsStream(m *Miniredis) {
m.srv.Register("XADD", m.cmdXadd)
m.srv.Register("XLEN", m.cmdXlen)
m.srv.Register("XREAD", m.cmdXread)
m.srv.Register("XRANGE", m.makeCmdXrange(false))
m.srv.Register("XREVRANGE", m.makeCmdXrange(true))
m.srv.Register("XLEN", m.cmdXlen, server.ReadOnlyOption())
m.srv.Register("XREAD", m.cmdXread, server.ReadOnlyOption())
m.srv.Register("XRANGE", m.makeCmdXrange(false), server.ReadOnlyOption())
m.srv.Register("XREVRANGE", m.makeCmdXrange(true), server.ReadOnlyOption())
m.srv.Register("XGROUP", m.cmdXgroup)
m.srv.Register("XINFO", m.cmdXinfo)
m.srv.Register("XREADGROUP", m.cmdXreadgroup)
m.srv.Register("XACK", m.cmdXack)
m.srv.Register("XDEL", m.cmdXdel)
m.srv.Register("XPENDING", m.cmdXpending)
m.srv.Register("XPENDING", m.cmdXpending, server.ReadOnlyOption())
m.srv.Register("XTRIM", m.cmdXtrim)
m.srv.Register("XAUTOCLAIM", m.cmdXautoclaim)
m.srv.Register("XCLAIM", m.cmdXclaim)
Expand Down
14 changes: 7 additions & 7 deletions cmd_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ import (
// commandsString handles all string value operations.
func commandsString(m *Miniredis) {
m.srv.Register("APPEND", m.cmdAppend)
m.srv.Register("BITCOUNT", m.cmdBitcount)
m.srv.Register("BITCOUNT", m.cmdBitcount, server.ReadOnlyOption())
m.srv.Register("BITOP", m.cmdBitop)
m.srv.Register("BITPOS", m.cmdBitpos)
m.srv.Register("BITPOS", m.cmdBitpos, server.ReadOnlyOption())
m.srv.Register("DECRBY", m.cmdDecrby)
m.srv.Register("DECR", m.cmdDecr)
m.srv.Register("GETBIT", m.cmdGetbit)
m.srv.Register("GET", m.cmdGet)
m.srv.Register("GETBIT", m.cmdGetbit, server.ReadOnlyOption())
m.srv.Register("GET", m.cmdGet, server.ReadOnlyOption())
m.srv.Register("GETEX", m.cmdGetex)
m.srv.Register("GETRANGE", m.cmdGetrange)
m.srv.Register("GETRANGE", m.cmdGetrange, server.ReadOnlyOption())
m.srv.Register("GETSET", m.cmdGetset)
m.srv.Register("GETDEL", m.cmdGetdel)
m.srv.Register("INCRBYFLOAT", m.cmdIncrbyfloat)
m.srv.Register("INCRBY", m.cmdIncrby)
m.srv.Register("INCR", m.cmdIncr)
m.srv.Register("MGET", m.cmdMget)
m.srv.Register("MGET", m.cmdMget, server.ReadOnlyOption())
m.srv.Register("MSET", m.cmdMset)
m.srv.Register("MSETNX", m.cmdMsetnx)
m.srv.Register("PSETEX", m.cmdPsetex)
Expand All @@ -37,7 +37,7 @@ func commandsString(m *Miniredis) {
m.srv.Register("SET", m.cmdSet)
m.srv.Register("SETNX", m.cmdSetnx)
m.srv.Register("SETRANGE", m.cmdSetrange)
m.srv.Register("STRLEN", m.cmdStrlen)
m.srv.Register("STRLEN", m.cmdStrlen, server.ReadOnlyOption())
}

// SET
Expand Down
8 changes: 8 additions & 0 deletions miniredis.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@ func (m *Miniredis) Server() *server.Server {
return m.srv
}

// IsReadOnlyCommand checks if a command is marked as read-only
func (m *Miniredis) IsReadOnlyCommand(cmd string) bool {
if m.srv == nil {
return false
}
return m.srv.IsReadOnlyCommand(cmd)
}

// Dump returns a text version of the selected DB, usable for debugging.
//
// Dump limits the maximum length of each key:value to "DumpMaxLineLen" characters.
Expand Down
17 changes: 17 additions & 0 deletions server/cmdmeta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package server

// cmdMeta holds metadata about a registered command
type cmdMeta struct {
handler Cmd
readOnly bool
}

// CmdOption is a function that configures command metadata
type CmdOption func(*cmdMeta)

// ReadOnlyOption marks a command as read-only
func ReadOnlyOption() CmdOption {
return func(meta *cmdMeta) {
meta.readOnly = true
}
}
32 changes: 26 additions & 6 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Hook func(*Peer, string, ...string) bool
// Server is a simple redis server
type Server struct {
l net.Listener
cmds map[string]Cmd
cmds map[string]*cmdMeta
preHook Hook
peers map[net.Conn]struct{}
mu sync.Mutex
Expand Down Expand Up @@ -62,7 +62,7 @@ func NewServerTLS(addr string, cfg *tls.Config) (*Server, error) {

func newServer(l net.Listener) *Server {
s := Server{
cmds: map[string]Cmd{},
cmds: map[string]*cmdMeta{},
peers: map[net.Conn]struct{}{},
l: l,
}
Expand Down Expand Up @@ -143,14 +143,23 @@ func (s *Server) Close() {

// Register a command. It can't have been registered before. Safe to call on a
// running server.
func (s *Server) Register(cmd string, f Cmd) error {
func (s *Server) Register(cmd string, f Cmd, options ...CmdOption) error {
s.mu.Lock()
defer s.mu.Unlock()
cmd = strings.ToUpper(cmd)
if _, ok := s.cmds[cmd]; ok {
return fmt.Errorf("command already registered: %s", cmd)
}
s.cmds[cmd] = f

meta := &cmdMeta{
handler: f,
readOnly: false,
}
for _, option := range options {
option(meta)
}
s.cmds[cmd] = meta

return nil
}

Expand Down Expand Up @@ -205,7 +214,7 @@ func (s *Server) Dispatch(c *Peer, args []string) {
}

s.mu.Lock()
cb, ok := s.cmds[cmdUp]
cmdMeta, ok := s.cmds[cmdUp]
s.mu.Unlock()
if !ok {
c.WriteError(errUnknownCommand(cmd, args))
Expand All @@ -215,7 +224,7 @@ func (s *Server) Dispatch(c *Peer, args []string) {
s.mu.Lock()
s.infoCmds++
s.mu.Unlock()
cb(c, cmdUp, args)
cmdMeta.handler(c, cmdUp, args)
if c.SwitchResp3 != nil {
c.Resp3 = *c.SwitchResp3
c.SwitchResp3 = nil
Expand All @@ -229,6 +238,17 @@ func (s *Server) TotalCommands() int {
return s.infoCmds
}

// IsReadOnlyCommand checks if a command is marked as read-only
func (s *Server) IsReadOnlyCommand(cmd string) bool {
s.mu.Lock()
defer s.mu.Unlock()
cmdUp := strings.ToUpper(cmd)
if cmdMeta, ok := s.cmds[cmdUp]; ok {
return cmdMeta.readOnly
}
return false
}

// ClientsLen gives the number of connected clients right now
func (s *Server) ClientsLen() int {
s.mu.Lock()
Expand Down
Loading