class MockRedis::Database

Attributes

data[R]
expire_times[R]

Public Class Methods

new(base, *_args) click to toggle source
# File lib/mock_redis/database.rb, line 34
def initialize(base, *_args)
  @base = base
  @data = MockRedis::IndifferentHash.new
  @expire_times = []
end

Public Instance Methods

auth(_) click to toggle source
# File lib/mock_redis/database.rb, line 55
def auth(_)
  'OK'
end
bgrewriteaof() click to toggle source
# File lib/mock_redis/database.rb, line 59
def bgrewriteaof
  'Background append only file rewriting started'
end
bgsave() click to toggle source
# File lib/mock_redis/database.rb, line 63
def bgsave
  'Background saving started'
end
call(command, &_block) click to toggle source

FIXME: Current implementation of ‘call` does not work propetly with kwarg-options. i.e. `call(“EXPIRE”, “foo”, 40, “NX”)` (which redis-rb will simply transmit to redis-server) will be passed to `#expire` without keywords transformation.

# File lib/mock_redis/database.rb, line 51
def call(command, &_block)
  public_send(command[0].downcase, *command[1..])
end
close()
Also aliased as: disconnect!
Alias for: disconnect
connected?() click to toggle source
# File lib/mock_redis/database.rb, line 73
def connected?
  true
end
dbsize() click to toggle source
# File lib/mock_redis/database.rb, line 77
def dbsize
  data.keys.length
end
del(*keys) click to toggle source
# File lib/mock_redis/database.rb, line 81
def del(*keys)
  keys = keys.flatten.map(&:to_s)
  # assert_has_args(keys, 'del') # no longer errors in redis > v4.5

  keys.
    find_all { |key| data[key] }.
    each { |k| persist(k) }.
    each { |k| data.delete(k) }.
    length
end
Also aliased as: unlink
disconnect() click to toggle source
# File lib/mock_redis/database.rb, line 67
def disconnect
  nil
end
Also aliased as: close
disconnect!()
Alias for: close
dump(key) click to toggle source
# File lib/mock_redis/database.rb, line 153
def dump(key)
  value = data[key]
  value ? Marshal.dump(value) : nil
end
echo(msg) click to toggle source
# File lib/mock_redis/database.rb, line 93
def echo(msg)
  msg.to_s
end
eval(*args) click to toggle source
# File lib/mock_redis/database.rb, line 300
def eval(*args); end
evalsha(*args) click to toggle source
# File lib/mock_redis/database.rb, line 298
def evalsha(*args); end
exists(*keys) click to toggle source
# File lib/mock_redis/database.rb, line 139
def exists(*keys)
  keys.count { |key| data.key?(key) }
end
exists?(*keys) click to toggle source
# File lib/mock_redis/database.rb, line 143
def exists?(*keys)
  keys.each { |key| return true if data.key?(key) }
  false
end
expire(key, seconds, nx: nil, xx: nil, lt: nil, gt: nil) click to toggle source
# File lib/mock_redis/database.rb, line 97
def expire(key, seconds, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
  assert_valid_integer(seconds)

  pexpire(key, seconds.to_i * 1000, nx: nx, xx: xx, lt: lt, gt: gt)
end
expire_keys() click to toggle source

This method isn’t private, but it also isn’t a Redis command, so it doesn’t belong up above with all the Redis commands.

# File lib/mock_redis/database.rb, line 396
def expire_keys
  now_sec, miliseconds = now
  now_ms = now_sec * 1_000 + miliseconds

  to_delete = expire_times.take_while do |(time, _key)|
    (time.to_r * 1_000).to_i <= now_ms
  end

  to_delete.each do |(_time, key)|
    del(key)
  end
end
expireat(key, timestamp, nx: nil, xx: nil, lt: nil, gt: nil) click to toggle source
# File lib/mock_redis/database.rb, line 111
def expireat(key, timestamp, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
  assert_valid_integer(timestamp)

  pexpireat(key, timestamp.to_i * 1000, nx: nx, xx: xx, lt: lt, gt: gt)
end
flushdb() click to toggle source
# File lib/mock_redis/database.rb, line 148
def flushdb
  data.each_key { |k| del(k) }
  'OK'
end
initialize_copy(_source) click to toggle source
# File lib/mock_redis/database.rb, line 40
def initialize_copy(_source)
  @data = @data.clone
  @data.each_key { |k| @data[k] = @data[k].clone }
  @expire_times = @expire_times.map(&:clone)
end
keys(format = '*') click to toggle source
# File lib/mock_redis/database.rb, line 169
def keys(format = '*')
  data.keys.grep(redis_pattern_to_ruby_regex(format))
end
lastsave() click to toggle source
# File lib/mock_redis/database.rb, line 187
def lastsave
  now.first
end
now() click to toggle source
# File lib/mock_redis/database.rb, line 269
def now
  current_time = @base.options[:time_class].now
  miliseconds = (current_time.to_r - current_time.to_i) * 1_000
  [current_time.to_i, miliseconds.to_i]
end
Also aliased as: time
persist(key) click to toggle source
# File lib/mock_redis/database.rb, line 191
def persist(key)
  if exists?(key) && has_expiration?(key)
    remove_expiration(key)
    true
  else
    false
  end
end
pexpire(key, ms, nx: nil, xx: nil, lt: nil, gt: nil) click to toggle source
# File lib/mock_redis/database.rb, line 103
def pexpire(key, ms, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
  assert_valid_integer(ms)

  now, miliseconds = @base.now
  now_ms = (now * 1000) + miliseconds
  pexpireat(key, now_ms + ms.to_i, nx: nx, xx: xx, lt: lt, gt: gt)
end
pexpireat(key, timestamp_ms, nx: nil, xx: nil, lt: nil, gt: nil) click to toggle source
# File lib/mock_redis/database.rb, line 117
    def pexpireat(key, timestamp_ms, nx: nil, xx: nil, lt: nil, gt: nil) # rubocop:disable Metrics/ParameterLists
      assert_valid_integer(timestamp_ms)

      if nx && gt || gt && lt || lt && nx || nx && xx
        raise Redis::CommandError, <<~TXT.chomp
          ERR NX and XX, GT or LT options at the same time are not compatible
        TXT
      end

      return false unless exists?(key)

      expiry = expiration(key)
      new_expiry = @base.time_at(Rational(timestamp_ms.to_i, 1000))

      if should_update_expiration?(expiry, new_expiry, nx: nx, xx: xx, lt: lt, gt: gt)
        set_expiration(key, new_expiry)
        true
      else
        false
      end
    end
ping(response = 'PONG') click to toggle source
# File lib/mock_redis/database.rb, line 200
def ping(response = 'PONG')
  response
end
pttl(key) click to toggle source
# File lib/mock_redis/database.rb, line 256
def pttl(key)
  now, miliseconds = @base.now
  now_ms = now * 1000 + miliseconds

  if !exists?(key)
    -2
  elsif has_expiration?(key)
    (expiration(key).to_r * 1000).to_i - now_ms
  else
    -1
  end
end
quit() click to toggle source
# File lib/mock_redis/database.rb, line 204
def quit
  'OK'
end
randomkey() click to toggle source
# File lib/mock_redis/database.rb, line 208
def randomkey
  data.keys[rand(data.length)]
end
rename(key, newkey) click to toggle source
# File lib/mock_redis/database.rb, line 212
def rename(key, newkey)
  unless data.include?(key)
    raise Redis::CommandError, 'ERR no such key'
  end

  if key != newkey
    data[newkey] = data.delete(key)
    if has_expiration?(key)
      set_expiration(newkey, expiration(key))
      remove_expiration(key)
    end
  end

  'OK'
end
renamenx(key, newkey) click to toggle source
# File lib/mock_redis/database.rb, line 228
def renamenx(key, newkey)
  unless data.include?(key)
    raise Redis::CommandError, 'ERR no such key'
  end

  if exists?(newkey)
    false
  else
    rename(key, newkey)
    true
  end
end
restore(key, ttl, value, replace: false) click to toggle source
# File lib/mock_redis/database.rb, line 158
def restore(key, ttl, value, replace: false)
  if !replace && exists?(key)
    raise Redis::CommandError, 'BUSYKEY Target key name already exists.'
  end
  data[key] = Marshal.load(value) # rubocop:disable Security/MarshalLoad
  if ttl > 0
    pexpire(key, ttl)
  end
  'OK'
end
save() click to toggle source
# File lib/mock_redis/database.rb, line 241
def save
  'OK'
end
scan(cursor, opts = {}) click to toggle source
# File lib/mock_redis/database.rb, line 173
def scan(cursor, opts = {})
  common_scan(data.keys, cursor, opts)
end
scan_each(opts = {}, &block) click to toggle source
# File lib/mock_redis/database.rb, line 177
def scan_each(opts = {}, &block)
  return to_enum(:scan_each, opts) unless block_given?
  cursor = 0
  loop do
    cursor, keys = scan(cursor, opts)
    keys.each(&block)
    break if cursor == '0'
  end
end
script(subcommand, *args) click to toggle source
# File lib/mock_redis/database.rb, line 296
def script(subcommand, *args); end
time()
Alias for: now
ttl(key) click to toggle source
# File lib/mock_redis/database.rb, line 245
def ttl(key)
  if !exists?(key)
    -2
  elsif has_expiration?(key)
    now, = @base.now
    expiration(key).to_i - now
  else
    -1
  end
end
type(key) click to toggle source
# File lib/mock_redis/database.rb, line 276
def type(key)
  if !exists?(key)
    'none'
  elsif hashy?(key)
    'hash'
  elsif stringy?(key)
    'string'
  elsif listy?(key)
    'list'
  elsif sety?(key)
    'set'
  elsif zsety?(key)
    'zset'
  elsif streamy?(key)
    'stream'
  else
    raise ArgumentError, "Not sure how #{data[key].inspect} got in here"
  end
end

Private Instance Methods

assert_valid_integer(integer) click to toggle source
# File lib/mock_redis/database.rb, line 304
def assert_valid_integer(integer)
  unless looks_like_integer?(integer.to_s)
    raise Redis::CommandError, 'ERR value is not an integer or out of range'
  end
  integer
end
assert_valid_timeout(timeout) click to toggle source
# File lib/mock_redis/database.rb, line 311
def assert_valid_timeout(timeout)
  if !looks_like_integer?(timeout.to_s)
    raise Redis::CommandError, 'ERR timeout is not an integer or out of range'
  elsif timeout < 0
    raise Redis::CommandError, 'ERR timeout is negative'
  end
  timeout
end
can_incr?(value) click to toggle source
# File lib/mock_redis/database.rb, line 320
def can_incr?(value)
  value.nil? || looks_like_integer?(value)
end
can_incr_float?(value) click to toggle source
# File lib/mock_redis/database.rb, line 324
def can_incr_float?(value)
  value.nil? || looks_like_float?(value)
end
expiration(key) click to toggle source
# File lib/mock_redis/database.rb, line 341
def expiration(key)
  expire_times.find { |(_, k)| k == key.to_s }&.first
end
extract_timeout(arglist) click to toggle source
# File lib/mock_redis/database.rb, line 328
def extract_timeout(arglist)
  options = arglist.last
  if options.is_a?(Hash) && options[:timeout]
    timeout = assert_valid_timeout(options[:timeout])
    [arglist[0..-2], timeout]
  elsif options.is_a?(Integer)
    timeout = assert_valid_timeout(options)
    [arglist[0..-2], timeout]
  else
    [arglist, 0]
  end
end
has_expiration?(key) click to toggle source
# File lib/mock_redis/database.rb, line 345
def has_expiration?(key)
  expire_times.any? { |(_, k)| k == key.to_s }
end
looks_like_float?(str) click to toggle source
# File lib/mock_redis/database.rb, line 353
def looks_like_float?(str)
  !!Float(str) rescue false
end
looks_like_integer?(str) click to toggle source
# File lib/mock_redis/database.rb, line 349
def looks_like_integer?(str)
  str =~ /^-?\d+$/
end
redis_pattern_to_ruby_regex(pattern) click to toggle source
# File lib/mock_redis/database.rb, line 365
def redis_pattern_to_ruby_regex(pattern)
  Regexp.new(
    "^#{pattern}$".
    gsub(/([+|(){}])/, '\\\\\1').
    gsub(/(?<!\\)\?/, '\\1.').
    gsub(/([^\\])\*/, '\\1.*')
  )
end
remove_expiration(key) click to toggle source
# File lib/mock_redis/database.rb, line 374
def remove_expiration(key)
  expire_times.delete_if do |(_t, k)|
    key.to_s == k
  end
end
set_expiration(key, time) click to toggle source
# File lib/mock_redis/database.rb, line 380
def set_expiration(key, time)
  remove_expiration(key)
  found = expire_times.each_with_index.to_a.bsearch { |item, _| item.first >= time }
  index = found ? found.last : -1
  expire_times.insert(index, [time, key.to_s])
end
should_update_expiration?(expiry, new_expiry, nx:, xx:, lt:, gt:) click to toggle source
# File lib/mock_redis/database.rb, line 357
def should_update_expiration?(expiry, new_expiry, nx:, xx:, lt:, gt:) # rubocop:disable Metrics/ParameterLists
  return false if nx && expiry || xx && !expiry
  return false if lt && expiry && new_expiry > expiry
  return false if gt && (!expiry || new_expiry < expiry)

  true
end
zero_pad(string, desired_length) click to toggle source
# File lib/mock_redis/database.rb, line 387
def zero_pad(string, desired_length)
  padding = "\000" * [(desired_length - string.length), 0].max
  string + padding
end