Add mset, msetnx, mget, incr, incrby, incrbyfloat, decr, and decrby convenience methods with unit tests

This commit is contained in:
Nathan Harris
2019-03-20 14:13:11 -07:00
parent 55fbbcc1f5
commit 4c7fe40419
2 changed files with 190 additions and 0 deletions
@@ -87,6 +87,16 @@ extension RedisCommandExecutor {
.map { return $0.string }
}
/// Returns the values of all specified keys, using `.null` to represent non-existant values.
///
/// See [https://redis.io/commands/mget](https://redis.io/commands/mget)
public func mget(_ keys: [String]) -> EventLoopFuture<[RESPValue]> {
assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "MGET", with: keys)
.mapFromRESP()
}
/// Set key to hold the string value.
/// If key already holds a value, it is overwritten, regardless of its type.
/// Any previous time to live associated with the key is discarded on successful SET operation.
@@ -96,4 +106,86 @@ extension RedisCommandExecutor {
return send(command: "SET", with: [key, value])
.map { _ in return () }
}
/// Sets each key to the respective new value, overwriting existing values.
///
/// - Note: Use `msetnx` if you don't want to overwrite values.
///
/// See [https://redis.io/commands/mset](https://redis.io/commands/mset)
public func mset(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Void> {
assert(operations.count > 0, "At least 1 key-value pair should be provided.")
let args = _convertMSET(operations)
return send(command: "MSET", with: args)
.map { _ in return () }
}
/// If every key does not exist, sets each key to the respective new value.
///
/// See [https://redis.io/commands/msetnx](https://redis.io/commands/msetnx)
public func msetnx(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Bool> {
assert(operations.count > 0, "At least 1 key-value pair should be provided.")
let args = _convertMSET(operations)
return send(command: "MSETNX", with: args)
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
@inline(__always)
private func _convertMSET(_ source: [String: RESPValueConvertible]) -> [RESPValueConvertible] {
return source.reduce(into: [RESPValueConvertible](), { (result, element) in
result.append(element.key)
result.append(element.value)
})
}
}
extension RedisCommandExecutor {
/// Increments the stored value by 1 and returns the new value.
///
/// See [https://redis.io/commands/incr](https://redis.io/commands/incr)
/// - Returns: The new value after the operation.
public func increment(_ key: String) -> EventLoopFuture<Int> {
return send(command: "INCR", with: [key])
.mapFromRESP()
}
/// Increments the stored value by the amount desired and returns the new value.
///
/// See [https://redis.io/commands/incrby](https://redis.io/commands/incrby)
/// - Returns: The new value after the operation.
public func increment(_ key: String, by count: Int) -> EventLoopFuture<Int> {
return send(command: "INCRBY", with: [key, count])
.mapFromRESP()
}
/// Increments the stored value by the amount desired and returns the new value.
///
/// See [https://redis.io/commands/incrbyfloat](https://redis.io/commands/incrbyfloat)
/// - Returns: The new value after the operation.
public func increment<T: BinaryFloatingPoint>(_ key: String, by count: T) -> EventLoopFuture<T>
where T: RESPValueConvertible
{
return send(command: "INCRBYFLOAT", with: [key, count])
.mapFromRESP()
}
/// Decrements the stored value by 1 and returns the new value.
///
/// See [https://redis.io/commands/decr](https://redis.io/commands/decr)
/// - Returns: The new value after the operation.
public func decrement(_ key: String) -> EventLoopFuture<Int> {
return send(command: "DECR", with: [key])
.mapFromRESP()
}
/// Decrements the stored valye by the amount desired and returns the new value.
///
/// See [https://redis.io/commands/decrby](https://redis.io/commands/decrby)
/// - Returns: The new value after the operation.
public func decrement(_ key: String, by count: Int) -> EventLoopFuture<Int> {
return send(command: "DECRBY", with: [key, count])
.mapFromRESP()
}
}
@@ -100,6 +100,95 @@ final class BasicCommandsTests: XCTestCase {
XCTAssertEqual(first, "100")
}
func test_increment() throws {
var result = try connection?.increment(#function).wait()
XCTAssertEqual(result, 1)
result = try connection?.increment(#function).wait()
XCTAssertEqual(result, 2)
}
func test_incrementBy() throws {
var result = try connection?.increment(#function, by: 10).wait()
XCTAssertEqual(result, 10)
result = try connection?.increment(#function, by: -3).wait()
XCTAssertEqual(result, 7)
result = try connection?.increment(#function, by: 0).wait()
XCTAssertEqual(result, 7)
}
func test_incrementByFloat() throws {
var float = try connection?.increment(#function, by: Float(3.0)).wait()
XCTAssertEqual(float, 3.0)
float = try connection?.increment(#function, by: Float(-10.135901)).wait()
XCTAssertEqual(float, -7.135901)
var double = try connection?.increment(#function, by: Double(10.2839)).wait()
XCTAssertEqual(double, 3.147999)
double = try connection?.increment(#function, by: Double(15.2938)).wait()
XCTAssertEqual(double, 18.441799)
}
func test_decrement() throws {
var result = try connection?.decrement(#function).wait()
XCTAssertEqual(result, -1)
result = try connection?.decrement(#function).wait()
XCTAssertEqual(result, -2)
}
func test_decrementBy() throws {
var result = try connection?.decrement(#function, by: -10).wait()
XCTAssertEqual(result, 10)
result = try connection?.decrement(#function, by: 3).wait()
XCTAssertEqual(result, 7)
result = try connection?.decrement(#function, by: 0).wait()
XCTAssertEqual(result, 7)
}
func test_mget() throws {
let keys = ["one", "two"]
try keys.forEach { _ = try connection?.set($0, to: $0).wait() }
let values = try connection?.mget(keys + ["empty"]).wait()
XCTAssertEqual(values?.count, 3)
XCTAssertEqual(values?[0].string, "one")
XCTAssertEqual(values?[1].string, "two")
XCTAssertEqual(values?[2].isNull, true)
XCTAssertEqual(try connection?.mget(["empty", #function]).wait().count, 2)
}
func test_mset() throws {
let data = [
"first": 1,
"second": 2
]
XCTAssertNoThrow(try connection?.mset(data).wait())
let values = try connection?.mget(["first", "second"]).wait().compactMap { $0.string }
XCTAssertEqual(values?.count, 2)
XCTAssertEqual(values?[0], "1")
XCTAssertEqual(values?[1], "2")
XCTAssertNoThrow(try connection?.mset(["first": 10]).wait())
let val = try connection?.get("first").wait()
XCTAssertEqual(val, "10")
}
func test_msetnx() throws {
let data = [
"first": 1,
"second": 2
]
var success = try connection?.msetnx(data).wait()
XCTAssertEqual(success, true)
success = try connection?.msetnx(["first": 10, "second": 20]).wait()
XCTAssertEqual(success, false)
let values = try connection?.mget(["first", "second"]).wait().compactMap { $0.string }
XCTAssertEqual(values?[0], "1")
XCTAssertEqual(values?[1], "2")
}
static var allTests = [
("test_select", test_select),
("test_set", test_set),
@@ -108,5 +197,14 @@ final class BasicCommandsTests: XCTestCase {
("test_delete", test_delete),
("test_ping", test_ping),
("test_echo", test_echo),
("test_swapdb", test_swapdb),
("test_increment", test_increment),
("test_incrementBy", test_incrementBy),
("test_incrementByFloat", test_incrementByFloat),
("test_decrement", test_decrement),
("test_decrementBy", test_decrementBy),
("test_mget", test_mget),
("test_mset", test_mset),
("test_msetnx", test_msetnx),
]
}