mirror of
https://github.com/swift-server/RediStack.git
synced 2026-06-02 07:37:33 +00:00
Add Blocking List Pop Commands
Motivation: To be a comprehensive library, all commands should be implemented, even if they are highly discouraged. List's collection of commands were missing `brpop`, `blpop`, and `brpoplpush`. Modifications: `brpop`, `blpop` and `brpoplpush` are supported with defaults and overloads for an easier API. Result: Users now have access to `brpop`, `blpop` and `brpoplpush` commands.
This commit is contained in:
@@ -117,6 +117,33 @@ extension RedisClient {
|
||||
public func rpoplpush(from source: String, to dest: String) -> EventLoopFuture<RESPValue> {
|
||||
return send(command: "RPOPLPUSH", with: [source, dest])
|
||||
}
|
||||
|
||||
/// Pops the last element from a source list and pushes it to a destination list, blocking until
|
||||
/// an element is available from the source list.
|
||||
///
|
||||
/// - Important:
|
||||
/// This will block the connection from completing further commands until an element
|
||||
/// is available to pop from the source list.
|
||||
///
|
||||
/// It is **highly** recommended to set a reasonable `timeout`
|
||||
/// or to use the non-blocking `rpoplpush` method where possible.
|
||||
///
|
||||
/// See [https://redis.io/commands/brpoplpush](https://redis.io/commands/brpoplpush)
|
||||
/// - Parameters:
|
||||
/// - source: The key of the list to pop from.
|
||||
/// - dest: The key of the list to push to.
|
||||
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
||||
/// - Returns: The element popped from the source list and pushed to the destination,
|
||||
/// or `nil` if the timeout was reached.
|
||||
@inlinable
|
||||
public func brpoplpush(
|
||||
from source: String,
|
||||
to dest: String,
|
||||
timeout: Int = 0
|
||||
) -> EventLoopFuture<RESPValue?> {
|
||||
return send(command: "BRPOPLPUSH", with: [source, dest, timeout])
|
||||
.map { $0.isNull ? nil: $0 }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Insert
|
||||
@@ -250,3 +277,116 @@ extension RedisClient {
|
||||
.mapFromRESP()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Blocking Pop
|
||||
|
||||
extension RedisClient {
|
||||
/// Removes the first element of a list, blocking until an element is available.
|
||||
///
|
||||
/// - Important:
|
||||
/// This will block the connection from completing further commands until an element
|
||||
/// is available to pop from the list.
|
||||
///
|
||||
/// It is **highly** recommended to set a reasonable `timeout`
|
||||
/// or to use the non-blocking `lpop` method where possible.
|
||||
///
|
||||
/// See [https://redis.io/commands/blpop](https://redis.io/commands/blpop)
|
||||
/// - Parameters:
|
||||
/// - key: The key of the list to pop from.
|
||||
/// - Returns: The element that was popped from the list, or `nil` if the timout was reached.
|
||||
@inlinable
|
||||
public func blpop(from key: String, timeout: Int = 0) -> EventLoopFuture<RESPValue?> {
|
||||
return blpop(from: [key], timeout: timeout)
|
||||
.map { $0?.1 }
|
||||
}
|
||||
|
||||
/// Removes the first element of a list, blocking until an element is available.
|
||||
///
|
||||
/// - Important:
|
||||
/// This will block the connection from completing further commands until an element
|
||||
/// is available to pop from the group of lists.
|
||||
///
|
||||
/// It is **highly** recommended to set a reasonable `timeout`
|
||||
/// or to use the non-blocking `lpop` method where possible.
|
||||
///
|
||||
/// See [https://redis.io/commands/blpop](https://redis.io/commands/blpop)
|
||||
/// - Parameters:
|
||||
/// - keys: The keys of lists in Redis that should be popped from.
|
||||
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
||||
/// - Returns:
|
||||
/// If timeout was reached, `nil`.
|
||||
///
|
||||
/// Otherwise, the key of the list the element was removed from and the popped element.
|
||||
@inlinable
|
||||
public func blpop(
|
||||
from keys: [String],
|
||||
timeout: Int = 0
|
||||
) -> EventLoopFuture<(String, RESPValue)?> {
|
||||
return _bpop(command: "BLPOP", keys, timeout)
|
||||
}
|
||||
|
||||
/// Removes the last element of a list, blocking until an element is available.
|
||||
///
|
||||
/// - Important:
|
||||
/// This will block the connection from completing further commands until an element
|
||||
/// is available to pop from the list.
|
||||
///
|
||||
/// It is **highly** recommended to set a reasonable `timeout`
|
||||
/// or to use the non-blocking `rpop` method where possible.
|
||||
///
|
||||
/// See [https://redis.io/commands/brpop](https://redis.io/commands/brpop)
|
||||
/// - Parameters:
|
||||
/// - key: The key of the list to pop from.
|
||||
/// - Returns: The element that was popped from the list, or `nil` if the timout was reached.
|
||||
@inlinable
|
||||
public func brpop(from key: String, timeout: Int = 0) -> EventLoopFuture<RESPValue?> {
|
||||
return brpop(from: [key], timeout: timeout)
|
||||
.map { $0?.1 }
|
||||
}
|
||||
|
||||
/// Removes the last element of a list, blocking until an element is available.
|
||||
///
|
||||
/// - Important:
|
||||
/// This will block the connection from completing further commands until an element
|
||||
/// is available to pop from the group of lists.
|
||||
///
|
||||
/// It is **highly** recommended to set a reasonable `timeout`
|
||||
/// or to use the non-blocking `rpop` method where possible.
|
||||
///
|
||||
/// See [https://redis.io/commands/brpop](https://redis.io/commands/brpop)
|
||||
/// - Parameters:
|
||||
/// - keys: The keys of lists in Redis that should be popped from.
|
||||
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
||||
/// - Returns:
|
||||
/// If timeout was reached, `nil`.
|
||||
///
|
||||
/// Otherwise, the key of the list the element was removed from and the popped element.
|
||||
@inlinable
|
||||
public func brpop(
|
||||
from keys: [String],
|
||||
timeout: Int = 0
|
||||
) -> EventLoopFuture<(String, RESPValue)?> {
|
||||
return _bpop(command: "BRPOP", keys, timeout)
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
func _bpop(
|
||||
command: String,
|
||||
_ keys: [String],
|
||||
_ timeout: Int
|
||||
) -> EventLoopFuture<(String, RESPValue)?> {
|
||||
let args = keys as [RESPValueConvertible] + [timeout]
|
||||
return send(command: command, with: args)
|
||||
.flatMapThrowing {
|
||||
guard !$0.isNull else { return nil }
|
||||
guard let response = [RESPValue]($0) else {
|
||||
throw NIORedisError.responseConversion(to: [RESPValue].self)
|
||||
}
|
||||
assert(response.count == 2, "Unexpected response size returned!")
|
||||
guard let key = response[0].string else {
|
||||
throw NIORedisError.assertionFailure(message: "Unexpected structure in response: \(response)")
|
||||
}
|
||||
return (key, response[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,22 @@ final class ListCommandsTests: XCTestCase {
|
||||
XCTAssertEqual(try connection.llen(of: "second").wait(), 1)
|
||||
}
|
||||
|
||||
func test_brpoplpush() throws {
|
||||
_ = try connection.lpush([10], into: "first").wait()
|
||||
|
||||
let element = try connection.brpoplpush(from: "first", to: "second").wait() ?? .null
|
||||
XCTAssertEqual(Int(element), 10)
|
||||
|
||||
let blockingConnection = try Redis.makeConnection().wait()
|
||||
let expectation = XCTestExpectation(description: "brpoplpush should never return")
|
||||
_ = blockingConnection.bzpopmin(from: #function)
|
||||
.always { _ in expectation.fulfill() }
|
||||
|
||||
let result = XCTWaiter.wait(for: [expectation], timeout: 1)
|
||||
XCTAssertEqual(result, .timedOut)
|
||||
try blockingConnection.channel.close().wait()
|
||||
}
|
||||
|
||||
func test_linsert() throws {
|
||||
_ = try connection.lpush([10], into: #function).wait()
|
||||
|
||||
@@ -129,6 +145,27 @@ final class ListCommandsTests: XCTestCase {
|
||||
XCTAssertEqual(Int(element), 30)
|
||||
}
|
||||
|
||||
func test_blpop() throws {
|
||||
let nilPop = try connection.blpop(from: #function, timeout: 1).wait()
|
||||
XCTAssertNil(nilPop)
|
||||
|
||||
_ = try connection.lpush([10, 20, 30], into: "first").wait()
|
||||
let pop1 = try connection.blpop(from: "first").wait() ?? .null
|
||||
XCTAssertEqual(Int(pop1), 30)
|
||||
|
||||
let pop2 = try connection.blpop(from: ["fake", "first"]).wait()
|
||||
XCTAssertEqual(pop2?.0, "first")
|
||||
|
||||
let blockingConnection = try Redis.makeConnection().wait()
|
||||
let expectation = XCTestExpectation(description: "blpop should never return")
|
||||
_ = blockingConnection.bzpopmin(from: #function)
|
||||
.always { _ in expectation.fulfill() }
|
||||
|
||||
let result = XCTWaiter.wait(for: [expectation], timeout: 1)
|
||||
XCTAssertEqual(result, .timedOut)
|
||||
try blockingConnection.channel.close().wait()
|
||||
}
|
||||
|
||||
func test_lpush() throws {
|
||||
_ = try connection.rpush([10, 20, 30], into: #function).wait()
|
||||
|
||||
@@ -165,6 +202,27 @@ final class ListCommandsTests: XCTestCase {
|
||||
XCTAssertTrue(result.isNull)
|
||||
}
|
||||
|
||||
func test_brpop() throws {
|
||||
let nilPop = try connection.brpop(from: #function, timeout: 1).wait()
|
||||
XCTAssertNil(nilPop)
|
||||
|
||||
_ = try connection.lpush([10, 20, 30], into: "first").wait()
|
||||
let pop1 = try connection.brpop(from: "first").wait() ?? .null
|
||||
XCTAssertEqual(Int(pop1), 10)
|
||||
|
||||
let pop2 = try connection.brpop(from: ["fake", "first"]).wait()
|
||||
XCTAssertEqual(pop2?.0, "first")
|
||||
|
||||
let blockingConnection = try Redis.makeConnection().wait()
|
||||
let expectation = XCTestExpectation(description: "brpop should never return")
|
||||
_ = blockingConnection.bzpopmin(from: #function)
|
||||
.always { _ in expectation.fulfill() }
|
||||
|
||||
let result = XCTWaiter.wait(for: [expectation], timeout: 1)
|
||||
XCTAssertEqual(result, .timedOut)
|
||||
try blockingConnection.channel.close().wait()
|
||||
}
|
||||
|
||||
func test_rpush() throws {
|
||||
_ = try connection.lpush([10, 20, 30], into: #function).wait()
|
||||
|
||||
@@ -195,11 +253,14 @@ final class ListCommandsTests: XCTestCase {
|
||||
("test_lrem", test_lrem),
|
||||
("test_lrange", test_lrange),
|
||||
("test_rpoplpush", test_rpoplpush),
|
||||
("test_brpoplpush", test_brpoplpush),
|
||||
("test_linsert", test_linsert),
|
||||
("test_lpop", test_lpop),
|
||||
("test_blpop", test_blpop),
|
||||
("test_lpush", test_lpush),
|
||||
("test_lpushx", test_lpushx),
|
||||
("test_rpop", test_rpop),
|
||||
("test_brpop", test_brpop),
|
||||
("test_rpush", test_rpush),
|
||||
("test_rpushx", test_rpushx),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user