# frozen_string_literal: true
class Redis
module Commands
module Lists
# Get the length of a list.
#
# @param [String] key
# @return [Integer]
def llen(key)
send_command([:llen, key])
end
# Remove the first/last element in a list, append/prepend it to another list and return it.
#
# @param [String] source source key
# @param [String] destination destination key
# @param [String, Symbol] where_source from where to remove the element from the source list
# e.g. 'LEFT' - from head, 'RIGHT' - from tail
# @param [String, Symbol] where_destination where to push the element to the source list
# e.g. 'LEFT' - to head, 'RIGHT' - to tail
#
# @return [nil, String] the element, or nil when the source key does not exist
#
# @note This command comes in place of the now deprecated RPOPLPUSH.
# Doing LMOVE RIGHT LEFT is equivalent.
def lmove(source, destination, where_source, where_destination)
where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
send_command([:lmove, source, destination, where_source, where_destination])
end
# Remove the first/last element in a list and append/prepend it
# to another list and return it, or block until one is available.
#
# @example With timeout
# element = redis.blmove("foo", "bar", "LEFT", "RIGHT", timeout: 5)
# # => nil on timeout
# # => "element" on success
# @example Without timeout
# element = redis.blmove("foo", "bar", "LEFT", "RIGHT")
# # => "element"
#
# @param [String] source source key
# @param [String] destination destination key
# @param [String, Symbol] where_source from where to remove the element from the source list
# e.g. 'LEFT' - from head, 'RIGHT' - from tail
# @param [String, Symbol] where_destination where to push the element to the source list
# e.g. 'LEFT' - to head, 'RIGHT' - to tail
# @param [Hash] options
# - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
#
# @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
#
def blmove(source, destination, where_source, where_destination, timeout: 0)
where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
command = [:blmove, source, destination, where_source, where_destination, timeout]
send_blocking_command(command, timeout)
end
# Prepend one or more values to a list, creating the list if it doesn't exist
#
# @param [String] key
# @param [String, Array<String>] value string value, or array of string values to push
# @return [Integer] the length of the list after the push operation
def lpush(key, value)
send_command([:lpush, key, value])
end
# Prepend a value to a list, only if the list exists.
#
# @param [String] key
# @param [String] value
# @return [Integer] the length of the list after the push operation
def lpushx(key, value)
send_command([:lpushx, key, value])
end
# Append one or more values to a list, creating the list if it doesn't exist
#
# @param [String] key
# @param [String, Array<String>] value string value, or array of string values to push
# @return [Integer] the length of the list after the push operation
def rpush(key, value)
send_command([:rpush, key, value])
end
# Append a value to a list, only if the list exists.
#
# @param [String] key
# @param [String] value
# @return [Integer] the length of the list after the push operation
def rpushx(key, value)
send_command([:rpushx, key, value])
end
# Remove and get the first elements in a list.
#
# @param [String] key
# @param [Integer] count number of elements to remove
# @return [nil, String, Array<String>] the values of the first elements
def lpop(key, count = nil)
command = [:lpop, key]
command << Integer(count) if count
send_command(command)
end
# Remove and get the last elements in a list.
#
# @param [String] key
# @param [Integer] count number of elements to remove
# @return [nil, String, Array<String>] the values of the last elements
def rpop(key, count = nil)
command = [:rpop, key]
command << Integer(count) if count
send_command(command)
end
# Remove the last element in a list, append it to another list and return it.
#
# @param [String] source source key
# @param [String] destination destination key
# @return [nil, String] the element, or nil when the source key does not exist
def rpoplpush(source, destination)
send_command([:rpoplpush, source, destination])
end
# Remove and get the first element in a list, or block until one is available.
#
# @example With timeout
# list, element = redis.blpop("list", :timeout => 5)
# # => nil on timeout
# # => ["list", "element"] on success
# @example Without timeout
# list, element = redis.blpop("list")
# # => ["list", "element"]
# @example Blocking pop on multiple lists
# list, element = redis.blpop(["list", "another_list"])
# # => ["list", "element"]
#
# @param [String, Array<String>] keys one or more keys to perform the
# blocking pop on
# @param [Hash] options
# - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
#
# @return [nil, [String, String]]
# - `nil` when the operation timed out
# - tuple of the list that was popped from and element was popped otherwise
def blpop(*args)
_bpop(:blpop, args)
end
# Remove and get the last element in a list, or block until one is available.
#
# @param [String, Array<String>] keys one or more keys to perform the
# blocking pop on
# @param [Hash] options
# - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
#
# @return [nil, [String, String]]
# - `nil` when the operation timed out
# - tuple of the list that was popped from and element was popped otherwise
#
# @see #blpop
def brpop(*args)
_bpop(:brpop, args)
end
# Pop a value from a list, push it to another list and return it; or block
# until one is available.
#
# @param [String] source source key
# @param [String] destination destination key
# @param [Hash] options
# - `:timeout => [Float, Integer]`: timeout in seconds, defaults to no timeout
#
# @return [nil, String]
# - `nil` when the operation timed out
# - the element was popped and pushed otherwise
def brpoplpush(source, destination, timeout: 0)
command = [:brpoplpush, source, destination, timeout]
send_blocking_command(command, timeout)
end
# Pops one or more elements from the first non-empty list key from the list
# of provided key names. If lists are empty, blocks until timeout has passed.
#
# @example Popping a element
# redis.blmpop(1.0, 'list')
# #=> ['list', ['a']]
# @example With count option
# redis.blmpop(1.0, 'list', count: 2)
# #=> ['list', ['a', 'b']]
#
# @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
# A timeout of zero can be used to block indefinitely.
# @params key [String, Array<String>] one or more keys with lists
# @params modifier [String]
# - when `"LEFT"` - the elements popped are those from the left of the list
# - when `"RIGHT"` - the elements popped are those from the right of the list
# @params count [Integer] a number of elements to pop
#
# @return [Array<String, Array<String, Float>>] list of popped elements or nil
def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
args = [:blmpop, timeout, keys.size, *keys, modifier]
args << "COUNT" << Integer(count) if count
send_blocking_command(args, timeout)
end
# Pops one or more elements from the first non-empty list key from the list
# of provided key names.
#
# @example Popping a element
# redis.lmpop('list')
# #=> ['list', ['a']]
# @example With count option
# redis.lmpop('list', count: 2)
# #=> ['list', ['a', 'b']]
#
# @params key [String, Array<String>] one or more keys with lists
# @params modifier [String]
# - when `"LEFT"` - the elements popped are those from the left of the list
# - when `"RIGHT"` - the elements popped are those from the right of the list
# @params count [Integer] a number of elements to pop
#
# @return [Array<String, Array<String, Float>>] list of popped elements or nil
def lmpop(*keys, modifier: "LEFT", count: nil)
raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
args = [:lmpop, keys.size, *keys, modifier]
args << "COUNT" << Integer(count) if count
send_command(args)
end
# Get an element from a list by its index.
#
# @param [String] key
# @param [Integer] index
# @return [String]
def lindex(key, index)
send_command([:lindex, key, Integer(index)])
end
# Insert an element before or after another element in a list.
#
# @param [String] key
# @param [String, Symbol] where `BEFORE` or `AFTER`
# @param [String] pivot reference element
# @param [String] value
# @return [Integer] length of the list after the insert operation, or `-1`
# when the element `pivot` was not found
def linsert(key, where, pivot, value)
send_command([:linsert, key, where, pivot, value])
end
# Get a range of elements from a list.
#
# @param [String] key
# @param [Integer] start start index
# @param [Integer] stop stop index
# @return [Array<String>]
def lrange(key, start, stop)
send_command([:lrange, key, Integer(start), Integer(stop)])
end
# Remove elements from a list.
#
# @param [String] key
# @param [Integer] count number of elements to remove. Use a positive
# value to remove the first `count` occurrences of `value`. A negative
# value to remove the last `count` occurrences of `value`. Or zero, to
# remove all occurrences of `value` from the list.
# @param [String] value
# @return [Integer] the number of removed elements
def lrem(key, count, value)
send_command([:lrem, key, Integer(count), value])
end
# Set the value of an element in a list by its index.
#
# @param [String] key
# @param [Integer] index
# @param [String] value
# @return [String] `OK`
def lset(key, index, value)
send_command([:lset, key, Integer(index), value])
end
# Trim a list to the specified range.
#
# @param [String] key
# @param [Integer] start start index
# @param [Integer] stop stop index
# @return [String] `OK`
def ltrim(key, start, stop)
send_command([:ltrim, key, Integer(start), Integer(stop)])
end
private
def _bpop(cmd, args, &blk)
timeout = if args.last.is_a?(Hash)
options = args.pop
options[:timeout]
end
timeout ||= 0
unless timeout.is_a?(Integer) || timeout.is_a?(Float)
raise ArgumentError, "timeout must be an Integer or Float, got: #{timeout.class}"
end
args.flatten!(1)
command = [cmd].concat(args)
command << timeout
send_blocking_command(command, timeout, &blk)
end
def _normalize_move_wheres(where_source, where_destination)
where_source = where_source.to_s.upcase
where_destination = where_destination.to_s.upcase
if where_source != "LEFT" && where_source != "RIGHT"
raise ArgumentError, "where_source must be 'LEFT' or 'RIGHT'"
end
if where_destination != "LEFT" && where_destination != "RIGHT"
raise ArgumentError, "where_destination must be 'LEFT' or 'RIGHT'"
end
[where_source, where_destination]
end
end
end
end