diff --git a/lib/redis/commands/lists.rb b/lib/redis/commands/lists.rb index c6383e47d..08a619a21 100644 --- a/lib/redis/commands/lists.rb +++ b/lib/redis/commands/lists.rb @@ -183,6 +183,34 @@ def brpoplpush(source, destination, timeout: 0) 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] 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>] 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 = [:lmpop, 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. # diff --git a/lib/redis/distributed.rb b/lib/redis/distributed.rb index e92d417fa..249ecd3cc 100644 --- a/lib/redis/distributed.rb +++ b/lib/redis/distributed.rb @@ -542,7 +542,14 @@ def ltrim(key, start, stop) node_for(key).ltrim(key, start, stop) end - # Iterate over keys, removing elements from the first non list set found. + # Iterate over keys, blocking and removing elements from the first non empty liist found. + def blmpop(timeout, *keys, modifier: "LEFT", count: nil) + ensure_same_node(:blmpop, keys) do |node| + node.blmpop(timeout, *keys, modifier: modifier, count: count) + end + end + + # Iterate over keys, removing elements from the first non list found. def lmpop(*keys, modifier: "LEFT", count: nil) ensure_same_node(:lmpop, keys) do |node| node.lmpop(*keys, modifier: modifier, count: count) diff --git a/test/lint/lists.rb b/test/lint/lists.rb index fdb37bbe7..ea18254df 100644 --- a/test/lint/lists.rb +++ b/test/lint/lists.rb @@ -203,6 +203,19 @@ def test_variadic_rpoplpush_expand assert_equal 'c', redis.rpoplpush('{1}foo', '{1}bar') end + def test_blmpop + target_version('7.0') do + assert_nil r.blmpop(1.0, '{1}foo') + + r.lpush('{1}foo', %w[a b c d e f g]) + assert_equal ['{1}foo', ['g']], r.blmpop(1.0, '{1}foo') + assert_equal ['{1}foo', ['f', 'e']], r.blmpop(1.0, '{1}foo', count: 2) + + r.lpush('{1}foo2', %w[a b]) + assert_equal ['{1}foo', ['a']], r.blmpop(1.0, '{1}foo', '{1}foo2', modifier: "RIGHT") + end + end + def test_lmpop target_version('7.0') do assert_nil r.lmpop('{1}foo')