Audit convenience commands

Motivation:

Performance, clarity, and uniformity with code documentation, Swift API, and data types.
This commit is contained in:
Nathan Harris
2019-03-23 23:28:41 -07:00
parent 601431ac9e
commit f4e646c1aa
6 changed files with 986 additions and 646 deletions
+55 -142
View File
@@ -7,6 +7,7 @@ extension RedisCommandExecutor {
/// See [https://redis.io/commands/echo](https://redis.io/commands/echo)
/// - Parameter message: The message to echo.
/// - Returns: The message sent with the command.
@inlinable
public func echo(_ message: String) -> EventLoopFuture<String> {
return send(command: "ECHO", with: [message])
.mapFromRESP()
@@ -15,8 +16,9 @@ extension RedisCommandExecutor {
/// Pings the server, which will respond with a message.
///
/// See [https://redis.io/commands/ping](https://redis.io/commands/ping)
/// - Parameter with: The optional message that the server should respond with.
/// - Parameter message: The optional message that the server should respond with.
/// - Returns: The provided message or Redis' default response of `"PONG"`.
@inlinable
public func ping(with message: String? = nil) -> EventLoopFuture<String> {
let arg = message != nil ? [message] : []
return send(command: "PING", with: arg)
@@ -26,196 +28,102 @@ extension RedisCommandExecutor {
/// Request for authentication in a password-protected Redis server.
///
/// [https://redis.io/commands/auth](https://redis.io/commands/auth)
/// - Parameter password: The password being used to access the Redis server.
/// - Returns: An `EventLoopFuture` that resolves when the connection has been authorized, or fails with a `RedisError`.
@inlinable
public func authorize(with password: String) -> EventLoopFuture<Void> {
return send(command: "AUTH", with: [password])
.map { _ in return () }
}
/// Select the Redis logical database having the specified zero-based numeric index.
/// New connections always use the database `0`.
/// - Note: New connections always use the database `0`.
///
/// [https://redis.io/commands/select](https://redis.io/commands/select)
public func select(database id: Int) -> EventLoopFuture<Void> {
return send(command: "SELECT", with: [id.description])
/// - Parameter index: The 0-based index of the database that will receive later commands.
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable
public func select(database index: Int) -> EventLoopFuture<Void> {
return send(command: "SELECT", with: [index])
.map { _ in return () }
}
/// Swaps the data of two Redis database by their index ID.
/// Swaps the data of two Redis databases by their index IDs.
///
/// See [https://redis.io/commands/swapdb](https://redis.io/commands/swapdb)
/// - Parameters:
/// - firstIndex: The index of the first database.
/// - secondIndex: The index of the second database.
/// - first: The index of the first database.
/// - second: The index of the second database.
/// - Returns: `true` if the swap was successful.
public func swapdb(firstIndex: Int, secondIndex: Int) -> EventLoopFuture<Bool> {
return send(command: "SWAPDB", with: [firstIndex, secondIndex])
@inlinable
public func swapDatabase(_ first: Int, with second: Int) -> EventLoopFuture<Bool> {
/// connection.swapDatabase(index: 0, withIndex: 10)
return send(command: "SWAPDB", with: [first, second])
.mapFromRESP(to: String.self)
.map { return $0 == "OK" }
}
}
extension RedisCommandExecutor {
/// Removes the specified keys. A key is ignored if it does not exist.
///
/// [https://redis.io/commands/del](https://redis.io/commands/del)
/// - Returns: A future number of keys that were removed.
public func delete(_ keys: String...) -> EventLoopFuture<Int> {
/// - Parameter keys: A list of keys to delete from the database.
/// - Returns: The number of keys deleted from the database.
@inlinable
public func delete(_ keys: [String]) -> EventLoopFuture<Int> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "DEL", with: keys)
.mapFromRESP()
}
/// Set a timeout on key. After the timeout has expired, the key will automatically be deleted.
/// A key with an associated timeout is often said to be volatile in Redis terminology.
/// Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
/// - Note: A key with an associated timeout is often said to be "volatile" in Redis terminology.
///
/// [https://redis.io/commands/expire](https://redis.io/commands/expire)
/// - Parameters:
/// - after: The lifetime (in seconds) the key will expirate at.
/// - Returns: A future bool indicating if the expiration was set or not.
public func expire(_ key: String, after deadline: Int) -> EventLoopFuture<Bool> {
return send(command: "EXPIRE", with: [key, deadline.description])
/// - key: The key to set the expiration on.
/// - deadline: The time from now the key will expire at.
/// - Returns: `true` if the expiration was set.
@inlinable
public func expire(_ key: String, after deadline: TimeAmount) -> EventLoopFuture<Bool> {
let amount = deadline.nanoseconds / 1_000_000_000
return send(command: "EXPIRE", with: [key, amount])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Get the value of a key.
/// If the key does not exist the value will be `nil`.
/// An error is resolved if the value stored at key is not a string, because GET only handles string values.
///
/// [https://redis.io/commands/get](https://redis.io/commands/get)
public func get(_ key: String) -> EventLoopFuture<String?> {
return send(command: "GET", with: [key])
.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.
///
/// [https://redis.io/commands/set](https://redis.io/commands/set)
public func set(_ key: String, to value: String) -> EventLoopFuture<Void> {
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()
}
}
// MARK: Scan
extension RedisCommandExecutor {
/// Incrementally iterates over all keys in the currently selected database.
///
/// [https://redis.io/commands/scan](https://redis.io/commands/scan)
/// - Parameters:
/// - startingFrom: The cursor position to start from.
/// - position: The cursor position to start from.
/// - count: The number of elements to advance by. Redis default is 10.
/// - matching: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of keys stored in the database.
/// - match: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of keys found in the database.
@inlinable
public func scan(
startingFrom pos: Int = 0,
startingFrom position: Int = 0,
count: Int? = nil,
matching match: String? = nil) -> EventLoopFuture<(Int, [String])>
{
return _scan(command: "SCAN", resultType: [String].self, nil, pos, count, match)
matching match: String? = nil
) -> EventLoopFuture<(Int, [String])> {
return _scan(command: "SCAN", nil, position, count, match)
}
@inline(__always)
@usableFromInline func _scan<T: RESPValueConvertible>(
@usableFromInline
func _scan<T>(
command: String,
resultType: T.Type,
resultType: T.Type = T.self,
_ key: String?,
_ pos: Int,
_ count: Int?,
_ match: String?) -> EventLoopFuture<(Int, T)>
_ match: String?
) -> EventLoopFuture<(Int, T)>
where
T: RESPValueConvertible
{
var args: [RESPValueConvertible] = [pos]
@@ -237,7 +145,12 @@ extension RedisCommandExecutor {
guard
let value = result[0].string,
let position = Int(value)
else { throw RedisError(identifier: #function, reason: "Unexpected value in response: \(result[0])") }
else {
throw RedisError(
identifier: #function,
reason: "Unexpected value in response: \(result[0])"
)
}
return position
}
let elements = response
+253 -177
View File
@@ -1,185 +1,10 @@
import NIO
extension RedisCommandExecutor {
/// Sets the hash field stored at the provided key with the value specified.
///
/// See [https://redis.io/commands/hset](https://redis.io/commands/hset)
/// - Returns: `true` if the hash was created, `false` if it was updated.
@inlinable
public func hset(_ key: String, field: String, to value: String) -> EventLoopFuture<Bool> {
return send(command: "HSET", with: [key, field, value])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Sets the specified fields to the values provided, overwriting existing values.
///
/// See [https://redis.io/commands/hmset](https://redis.io/commands/hmset)
@inlinable
public func hmset(_ key: String, to fields: [String: String]) -> EventLoopFuture<Void> {
assert(fields.count > 0, "At least 1 key-value pair should be specified")
let args: [RESPValueConvertible] = fields.reduce(into: [], { (result, element) in
result.append(element.key)
result.append(element.value)
})
return send(command: "HMSET", with: [key] + args)
.map { _ in () }
}
/// Sets the specified hash field to the value provided only if the field does not exist.
///
/// See [https://redis.io/commands/hsetnx](https://redis.io/commands/hsetnx)
/// - Returns: The success of setting the field's value.
@inlinable
public func hsetnx(_ key: String, field: String, to value: String) -> EventLoopFuture<Bool> {
return send(command: "HSETNX", with: [key, field, value])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Gets the value stored in the hash field at the key provided.
///
/// See [https://redis.io/commands/hget](https://redis.io/commands/hget)
@inlinable
public func hget(_ key: String, field: String) -> EventLoopFuture<String?> {
return send(command: "HGET", with: [key, field])
.map { return String($0) }
}
/// Returns the values stored in the fields specified at the key provided.
///
/// See [https://redis.io/commands/hmget](https://redis.io/commands/hmget)
/// - Returns: A list of values in the same order as the `fields` argument.
@inlinable
public func hmget(_ key: String, fields: [String]) -> EventLoopFuture<[String?]> {
assert(fields.count > 0, "At least 1 field should be specified")
return send(command: "HMGET", with: [key] + fields)
.mapFromRESP(to: [RESPValue].self)
.map { return $0.map(String.init) }
}
/// Returns all the fields and values stored at the provided key.
///
/// See [https://redis.io/commands/hgetall](https://redis.io/commands/hgetall)
/// - Returns: A key-value pair list of fields and their values.
@inlinable
public func hgetall(from key: String) -> EventLoopFuture<[String: String]> {
return send(command: "HGETALL", with: [key])
.mapFromRESP(to: [String].self)
.map(Self.mapHashResponseToDictionary)
}
/// Removes the specified fields from the hash stored at the key provided.
///
/// See [https://redis.io/commands/hdel](https://redis.io/commands/hdel)
/// - Returns: The number of fields that were deleted.
@inlinable
public func hdel(_ key: String, fields: [String]) -> EventLoopFuture<Int> {
assert(fields.count > 0, "At least 1 field should be specified")
return send(command: "HDEL", with: [key] + fields)
.mapFromRESP()
}
/// Checks if the provided key and field exist.
///
/// See [https://redis.io/commands/hexists](https://redis.io/commands/hexists)
@inlinable
public func hexists(_ key: String, field: String) -> EventLoopFuture<Bool> {
return send(command: "HEXISTS", with: [key, field])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Returns the number of fields contained in the hash stored at the key provided.
///
/// See [https://redis.io/commands/hlen](https://redis.io/commands/hlen)
/// - Returns: The number of fields in the hash, or 0 if the key doesn't exist.
@inlinable
public func hlen(of key: String) -> EventLoopFuture<Int> {
return send(command: "HLEN", with: [key])
.mapFromRESP()
}
/// Returns hash field's value length as a string, stored at the provided key.
///
/// See [https://redis.io/commands/hstrlen](https://redis.io/commands/hstrlen)
@inlinable
public func hstrlen(of key: String, field: String) -> EventLoopFuture<Int> {
return send(command: "HSTRLEN", with: [key, field])
.mapFromRESP()
}
/// Returns all field names in the hash stored at the key provided.
///
/// See [https://redis.io/commands/hkeys](https://redis.io/commands/hkeys)
/// - Returns: An array of field names, or an empty array.
@inlinable
public func hkeys(storedAt key: String) -> EventLoopFuture<[String]> {
return send(command: "HKEYS", with: [key])
.mapFromRESP()
}
/// Returns all of the field values stored in hash at the key provided.
///
/// See [https://redis.io/commands/hvals](https://redis.io/commands/hvals)
@inlinable
public func hvals(storedAt key: String) -> EventLoopFuture<[String]> {
return send(command: "HVALS", with: [key])
.mapFromRESP()
}
/// Increments the field value stored at the key provided, and returns the new value.
///
/// See [https://redis.io/commands/hincrby](https://redis.io/commands/hincrby)
@inlinable
public func hincrby(_ key: String, field: String, by amount: Int) -> EventLoopFuture<Int> {
return send(command: "HINCRBY", with: [key, field, amount])
.mapFromRESP()
}
/// Increments the field value stored at the key provided, and returns the new value.
///
/// See [https://redis.io/commands/hincrbyfloat](https://redis.io/commands/hincrbyfloat)
@inlinable
public func hincrbyfloat<T: BinaryFloatingPoint>(_ key: String, field: String, by amount: T) -> EventLoopFuture<T>
where T: RESPValueConvertible
{
return send(command: "HINCRBYFLOAT", with: [key, field, amount])
.mapFromRESP()
}
/// Incrementally iterates over all fields in the hash stored at the key provided.
///
/// [https://redis.io/commands/scan](https://redis.io/commands/scan)
/// - Parameters:
/// - key: The key of the hash.
/// - atPosition: The position to start the scan from.
/// - count: The number of elements to advance by. Redis default is 10.
/// - matching: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of values stored at the keys.
@inlinable
public func hscan(
_ key: String,
atPosition pos: Int = 0,
count: Int? = nil,
matching match: String? = nil) -> EventLoopFuture<(Int, [String: String])>
{
return _scan(command: "HSCAN", resultType: [String].self, key, pos, count, match)
.map {
let values = Self.mapHashResponseToDictionary($0.1)
return ($0.0, values)
}
}
}
// MARK: Static Helpers
extension RedisCommandExecutor {
@inline(__always)
@usableFromInline
static func mapHashResponseToDictionary(_ values: [String]) -> [String: String] {
static func _mapHashResponse(_ values: [String]) -> [String: String] {
guard values.count > 0 else { return [:] }
var result: [String: String] = [:]
@@ -195,3 +20,254 @@ extension RedisCommandExecutor {
return result
}
}
// MARK: General
extension RedisCommandExecutor {
/// Removes the specified fields from a hash.
///
/// See [https://redis.io/commands/hdel](https://redis.io/commands/hdel)
/// - Parameters:
/// - fields: The list of field names that should be removed from the hash.
/// - key: The key of the hash to delete from.
/// - Returns: The number of fields that were deleted.
@inlinable
public func hdel(_ fields: [String], from key: String) -> EventLoopFuture<Int> {
guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "HDEL", with: [key] + fields)
.mapFromRESP()
}
/// Checks if a hash contains the field specified.
///
/// See [https://redis.io/commands/hexists](https://redis.io/commands/hexists)
/// - Parameters:
/// - field: The field name to look for.
/// - key: The key of the hash to look within.
/// - Returns: `true` if the hash contains the field, `false` if either the key or field do not exist.
@inlinable
public func hexists(_ field: String, in key: String) -> EventLoopFuture<Bool> {
return send(command: "HEXISTS", with: [key, field])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Gets the number of fields contained in a hash.
///
/// See [https://redis.io/commands/hlen](https://redis.io/commands/hlen)
/// - Parameter key: The key of the hash to get field count of.
/// - Returns: The number of fields in the hash, or `0` if the key doesn't exist.
@inlinable
public func hlen(of key: String) -> EventLoopFuture<Int> {
return send(command: "HLEN", with: [key])
.mapFromRESP()
}
/// Gets the string length of a hash field's value.
///
/// See [https://redis.io/commands/hstrlen](https://redis.io/commands/hstrlen)
/// - Parameters:
/// - field: The field name whose value is being accessed.
/// - key: The key of the hash.
/// - Returns: The string length of the hash field's value, or `0` if the field or hash do not exist.
@inlinable
public func hstrlen(of field: String, in key: String) -> EventLoopFuture<Int> {
return send(command: "HSTRLEN", with: [key, field])
.mapFromRESP()
}
/// Gets all field names in a hash.
///
/// See [https://redis.io/commands/hkeys](https://redis.io/commands/hkeys)
/// - Parameter key: The key of the hash.
/// - Returns: A list of field names stored within the hash.
@inlinable
public func hkeys(in key: String) -> EventLoopFuture<[String]> {
return send(command: "HKEYS", with: [key])
.mapFromRESP()
}
/// Gets all values stored in a hash.
///
/// See [https://redis.io/commands/hvals](https://redis.io/commands/hvals)
/// - Parameter key: The key of the hash.
/// - Returns: A list of all values stored in a hash.
@inlinable
public func hvals(in key: String) -> EventLoopFuture<[RESPValue]> {
return send(command: "HVALS", with: [key])
.mapFromRESP()
}
/// Incrementally iterates over all fields in a hash.
///
/// [https://redis.io/commands/scan](https://redis.io/commands/scan)
/// - Parameters:
/// - key: The key of the hash.
/// - position: The position to start the scan from.
/// - count: The number of elements to advance by. Redis default is 10.
/// - match: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of found fields and their values.
@inlinable
public func hscan(
_ key: String,
startingFrom position: Int = 0,
count: Int? = nil,
matching match: String? = nil
) -> EventLoopFuture<(Int, [String: String])> {
return _scan(command: "HSCAN", resultType: [String].self, key, position, count, match)
.map {
let values = Self._mapHashResponse($0.1)
return ($0.0, values)
}
}
}
// MARK: Set
extension RedisCommandExecutor {
/// Sets a hash field to the value specified.
/// - Note: If you do not want to overwrite existing values, use `hsetnx(_:field:to:)`.
///
/// See [https://redis.io/commands/hset](https://redis.io/commands/hset)
/// - Parameters:
/// - field: The name of the field in the hash being set.
/// - value: The value the hash field should be set to.
/// - key: The key that holds the hash.
/// - Returns: `true` if the hash was created, `false` if it was updated.
@inlinable
public func hset(
_ field: String,
to value: RESPValueConvertible,
in key: String
) -> EventLoopFuture<Bool> {
return send(command: "HSET", with: [key, field, value])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Sets a hash field to the value specified only if the field does not currently exist.
/// - Note: If you do not care about overwriting existing values, use `hset(_:field:to:)`.
///
/// See [https://redis.io/commands/hsetnx](https://redis.io/commands/hsetnx)
/// - Parameters:
/// - field: The name of the field in the hash being set.
/// - value: The value the hash field should be set to.
/// - key: The key that holds the hash.
/// - Returns: `true` if the hash was created.
@inlinable
public func hsetnx(
_ field: String,
to value: RESPValueConvertible,
in key: String
) -> EventLoopFuture<Bool> {
return send(command: "HSETNX", with: [key, field, value])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Sets the fields in a hash to the respective values provided.
///
/// See [https://redis.io/commands/hmset](https://redis.io/commands/hmset)
/// - Parameters:
/// - fields: The key-value pair of field names and their respective values to set.
/// - key: The key that holds the hash.
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable
public func hmset(
_ fields: [String: RESPValueConvertible],
in key: String
) -> EventLoopFuture<Void> {
assert(fields.count > 0, "At least 1 key-value pair should be specified")
let args: [RESPValueConvertible] = fields.reduce(into: [], { (result, element) in
result.append(element.key)
result.append(element.value)
})
return send(command: "HMSET", with: [key] + args)
.map { _ in () }
}
}
// MARK: Get
extension RedisCommandExecutor {
/// Gets a hash field's value.
///
/// See [https://redis.io/commands/hget](https://redis.io/commands/hget)
/// - Parameters:
/// - field: The name of the field whose value is being accessed.
/// - key: The key of the hash being accessed.
/// - Returns: The value of the hash field, or `nil` if either the key or field does not exist.
@inlinable
public func hget(_ field: String, from key: String) -> EventLoopFuture<String?> {
return send(command: "HGET", with: [key, field])
.map { return String($0) }
}
/// Gets the values of a hash for the fields specified.
///
/// See [https://redis.io/commands/hmget](https://redis.io/commands/hmget)
/// - Parameters:
/// - fields: A list of field names to get values for.
/// - key: The key of the hash being accessed.
/// - Returns: A list of values in the same order as the `fields` argument. Non-existent fields return `nil` values.
@inlinable
public func hmget(_ fields: [String], from key: String) -> EventLoopFuture<[String?]> {
guard fields.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "HMGET", with: [key] + fields)
.mapFromRESP(to: [RESPValue].self)
.map { return $0.map(String.init) }
}
/// Returns all the fields and values stored in a hash.
///
/// See [https://redis.io/commands/hgetall](https://redis.io/commands/hgetall)
/// - Parameter key: The key of the hash to pull from.
/// - Returns: A key-value pair list of fields and their values.
@inlinable
public func hgetall(from key: String) -> EventLoopFuture<[String: String]> {
return send(command: "HGETALL", with: [key])
.mapFromRESP(to: [String].self)
.map(Self._mapHashResponse)
}
}
// MARK: Increment
extension RedisCommandExecutor {
/// Increments a hash field's value and returns the new value.
///
/// See [https://redis.io/commands/hincrby](https://redis.io/commands/hincrby)
/// - Parameters:
/// - amount: The amount to increment the value stored in the field by.
/// - field: The name of the field whose value should be incremented.
/// - key: The key of the hash the field is stored in.
/// - Returns: The new value of the hash field.
@inlinable
public func hincrby(_ amount: Int, field: String, in key: String) -> EventLoopFuture<Int> {
/// connection.hincrby(20, field: "foo", in: "key")
return send(command: "HINCRBY", with: [key, field, amount])
.mapFromRESP()
}
/// Increments a hash field's value and returns the new value.
///
/// See [https://redis.io/commands/hincrbyfloat](https://redis.io/commands/hincrbyfloat)
/// - Parameters:
/// - amount: The amount to increment the value stored in the field by.
/// - field: The name of the field whose value should be incremented.
/// - key: The key of the hash the field is stored in.
/// - Returns: The new value of the hash field.
@inlinable
public func hincrbyfloat<T>(_ amount: T, field: String, in key: String) -> EventLoopFuture<T>
where
T: BinaryFloatingPoint,
T: RESPValueConvertible
{
return send(command: "HINCRBYFLOAT", with: [key, field, amount])
.mapFromRESP()
}
}
+120 -59
View File
@@ -1,67 +1,103 @@
import NIO
// MARK: General
extension RedisCommandExecutor {
/// Returns the length of the list stored at the key provided.
/// Gets the length of a list.
///
/// See [https://redis.io/commands/llen](https://redis.io/commands/llen)
/// - Parameter key: The key of the list.
/// - Returns: The number of elements in the list.
@inlinable
public func llen(of key: String) -> EventLoopFuture<Int> {
return send(command: "LLEN", with: [key])
.mapFromRESP()
}
/// Returns the element at the specified index stored at the key provided.
/// Gets the element from a list stored at the provided index position.
///
/// See [https://redis.io/commands/llen](https://redis.io/commands/llen)
/// See [https://redis.io/commands/lindex](https://redis.io/commands/lindex)
/// - Parameters:
/// - index: The 0-based index of the element to get.
/// - key: The key of the list.
/// - Returns: The element stored at index, or `.null` if out of bounds.
@inlinable
public func lindex(_ key: String, index: Int) -> EventLoopFuture<RESPValue> {
public func lindex(_ index: Int, from key: String) -> EventLoopFuture<RESPValue> {
return send(command: "LINDEX", with: [key, index])
.flatMapThrowing { response in
guard response.isNull else { return response }
throw RedisError(identifier: #function, reason: "Index out of bounds.")
}
}
/// Sets the value at the specified index stored at the key provided.
/// Sets the value of an element in a list at the provided index position.
///
/// See [https://redis.io/commands/lset](https://redis.io/commands/lset)
/// - Parameters:
/// - index: The 0-based index of the element to set.
/// - value: The new value the element should be.
/// - key: The key of the list to update.
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable
public func lset(_ key: String, index: Int, to value: RESPValueConvertible) -> EventLoopFuture<Void> {
public func lset(
index: Int,
to value: RESPValueConvertible,
in key: String
) -> EventLoopFuture<Void> {
return send(command: "LSET", with: [key, index, value])
.map { _ in () }
}
/// Removes elements from the list matching the value provided, up to the count specified.
/// Removes elements from a list matching the value provided.
///
/// See [https://redis.io/commands/lrem](https://redis.io/commands/lrem)
/// - Returns: The number of elements removed.
/// - Parameters:
/// - value: The value to delete from the list.
/// - key: The key of the list to remove from.
/// - count: The max number of elements to remove matching the value. See Redis' documentation for more info.
/// - Returns: The number of elements removed from the list.
@inlinable
public func lrem(_ value: RESPValueConvertible, from key: String, count: Int) -> EventLoopFuture<Int> {
public func lrem(
_ value: RESPValueConvertible,
from key: String,
count: Int = 0
) -> EventLoopFuture<Int> {
return send(command: "LREM", with: [key, count, value])
.mapFromRESP()
}
/// Trims the list stored at the key provided to contain elements within the bounds of indexes specified.
/// Trims a list to only contain elements within the specified inclusive bounds of 0-based indices.
///
/// See [https://redis.io/commands/ltrim](https://redis.io/commands/ltrim)
/// - Parameters:
/// - key: The key of the list to trim.
/// - start: The index of the first element to keep.
/// - stop: The index of the last element to keep.
/// - Returns: An `EventLoopFuture` that resolves when the operation has succeeded, or fails with a `RedisError`.
@inlinable
public func ltrim(_ key: String, startIndex start: Int, endIndex end: Int) -> EventLoopFuture<Void> {
return send(command: "LTRIM", with: [key, start, end])
public func ltrim(_ key: String, before start: Int, after stop: Int) -> EventLoopFuture<Void> {
return send(command: "LTRIM", with: [key, start, stop])
.map { _ in () }
}
/// Returns the elements within the range bounds provided.
/// Gets all elements from a list within the the specified inclusive bounds of 0-based indices.
///
/// See [https://redis.io/commands/ltrim](https://redis.io/commands/ltrim)
/// See [https://redis.io/commands/lrange](https://redis.io/commands/lrange)
/// - Parameters:
/// - range: The range of inclusive indices of elements to get.
/// - key: The key of the list.
/// - Returns: A list of elements found within the range specified.
@inlinable
public func lrange(of key: String, startIndex start: Int, endIndex end: Int) -> EventLoopFuture<[RESPValue]> {
return send(command: "LRANGE", with: [key, start, end])
public func lrange(
within range: (startIndex: Int, endIndex: Int),
from key: String
) -> EventLoopFuture<[RESPValue]> {
return send(command: "LRANGE", with: [key, range.startIndex, range.endIndex])
.mapFromRESP()
}
/// Pops the last element from the source list and pushes it to the destination list.
/// Pops the last element from a source list and pushes it to a destination list.
///
/// See [https://redis.io/commands/rpoplpush](https://redis.io/commands/rpoplpush)
/// - Parameters:
/// - source: The key of the list to pop from.
/// - dest: The key of the list to push to.
/// - Returns: The element that was moved.
@inlinable
public func rpoplpush(from source: String, to dest: String) -> EventLoopFuture<RESPValue> {
@@ -72,36 +108,44 @@ extension RedisCommandExecutor {
// MARK: Insert
extension RedisCommandExecutor {
/// Inserts the value before the first element matching the pivot value provided.
/// Inserts the element before the first element matching the "pivot" value specified.
///
/// See [https://redis.io/commands/linsert](https://redis.io/commands/linsert)
/// - Parameters:
/// - element: The value to insert into the list.
/// - key: The key of the list.
/// - pivot: The value of the element to insert before.
/// - Returns: The size of the list after the insert, or -1 if an element matching the pivot value was not found.
@inlinable
public func linsert<T: RESPValueConvertible>(
_ value: T,
into key: String,
before pivot: T) -> EventLoopFuture<Int>
public func linsert<T>(_ element: T, into key: String, before pivot: T) -> EventLoopFuture<Int>
where T: RESPValueConvertible
{
return _linsert(pivotKeyword: "BEFORE", value, key, pivot)
return _linsert(pivotKeyword: "BEFORE", element, key, pivot)
}
/// Inserts the value after the first element matching the pivot value provided.
/// Inserts the element after the first element matching the "pivot" value provided.
///
/// See [https://redis.io/commands/linsert](https://redis.io/commands/linsert)
/// - Parameters:
/// - element: The value to insert into the list.
/// - key: The key of the list.
/// - pivot: The value of the element to insert after.
/// - Returns: The size of the list after the insert, or -1 if an element matching the pivot value was not found.
@inlinable
public func linsert<T: RESPValueConvertible>(
_ value: T,
into key: String,
after pivot: T) -> EventLoopFuture<Int>
public func linsert<T>(_ element: T, into key: String, after pivot: T) -> EventLoopFuture<Int>
where T: RESPValueConvertible
{
return _linsert(pivotKeyword: "AFTER", value, key, pivot)
return _linsert(pivotKeyword: "AFTER", element, key, pivot)
}
@inline(__always)
@usableFromInline
func _linsert(pivotKeyword: StaticString, _ value: RESPValueConvertible, _ key: String, _ pivot: RESPValueConvertible) -> EventLoopFuture<Int> {
return send(command: "LINSERT", with: [key, pivotKeyword.description, pivot, value])
func _linsert(
pivotKeyword: String,
_ element: RESPValueConvertible,
_ key: String,
_ pivot: RESPValueConvertible
) -> EventLoopFuture<Int> {
return send(command: "LINSERT", with: [key, pivotKeyword, pivot, element])
.mapFromRESP()
}
}
@@ -109,34 +153,43 @@ extension RedisCommandExecutor {
// MARK: Head Operations
extension RedisCommandExecutor {
/// Removes the first element in the list and returns it.
/// Removes the first element of a list.
///
/// See [https://redis.io/commands/lpop](https://redis.io/commands/lpop)
/// - Parameter key: The key of the list to pop from.
/// - Returns: The element that was popped from the list, or `.null`.
@inlinable
public func lpop(from key: String) -> EventLoopFuture<RESPValue?> {
public func lpop(from key: String) -> EventLoopFuture<RESPValue> {
return send(command: "LPOP", with: [key])
.mapFromRESP()
}
/// Inserts all values provided into the list stored at the key specified.
/// - Note: This inserts the values at the head of the list, for the tail see `rpush(_:to:)`.
/// Pushes all of the provided elements into a list.
/// - Note: This inserts the elements at the head of the list; for the tail see `rpush(_:into:)`.
///
/// See [https://redis.io/commands/lpush](https://redis.io/commands/lpush)
/// - Parameters:
/// - elements: The values to push into the list.
/// - key: The key of the list.
/// - Returns: The length of the list after adding the new elements.
@inlinable
public func lpush(_ values: [RESPValueConvertible], to key: String) -> EventLoopFuture<Int> {
return send(command: "LPUSH", with: [key] + values)
public func lpush(_ elements: [RESPValueConvertible], into key: String) -> EventLoopFuture<Int> {
assert(elements.count > 0, "At least 1 element should be provided.")
return send(command: "LPUSH", with: [key] + elements)
.mapFromRESP()
}
/// Inserts the value at the head of the list only if the key exists and holds a list.
/// - Note: This inserts the values at the head of the list, for the tail see `rpushx(_:to:)`.
/// Pushes an element into a list, but only if the key exists and holds a list.
/// - Note: This inserts the element at the head of the list, for the tail see `rpushx(_:into:)`.
///
/// See [https://redis.io/commands/lpushx](https://redis.io/commands/lpushx)
/// - Parameters:
/// - element: The value to try and push into the list.
/// - key: The key of the list.
/// - Returns: The length of the list after adding the new elements.
@inlinable
public func lpushx(_ value: RESPValueConvertible, to key: String) -> EventLoopFuture<Int> {
return send(command: "LPUSHX", with: [key, value])
public func lpushx(_ element: RESPValueConvertible, into key: String) -> EventLoopFuture<Int> {
return send(command: "LPUSHX", with: [key, element])
.mapFromRESP()
}
}
@@ -144,34 +197,42 @@ extension RedisCommandExecutor {
// MARK: Tail Operations
extension RedisCommandExecutor {
/// Removes the last element in the list and returns it.
/// Removes the last element a list.
///
/// See [https://redis.io/commands/rpop](https://redis.io/commands/rpop)
/// - Parameter key: The key of the list to pop from.
/// - Returns: The element that was popped from the list, else `.null`.
@inlinable
public func rpop(from key: String) -> EventLoopFuture<RESPValue?> {
public func rpop(from key: String) -> EventLoopFuture<RESPValue> {
return send(command: "RPOP", with: [key])
.mapFromRESP()
}
/// Inserts all values provided into the list stored at the key specified.
/// - Note: This inserts the values at the tail of the list, for the head see `lpush(_:to:)`.
/// Pushes all of the provided elements into a list.
/// - Note: This inserts the elements at the tail of the list; for the head see `lpush(_:into:)`.
///
/// See [https://redis.io/commands/rpush](https://redis.io/commands/rpush)
/// - Returns: The size of the list after adding the new elements.
/// - elements: The values to push into the list.
/// - key: The key of the list.
/// - Returns: The length of the list after adding the new elements.
@inlinable
public func rpush(_ values: [RESPValueConvertible], to key: String) -> EventLoopFuture<Int> {
return send(command: "RPUSH", with: [key] + values)
public func rpush(_ elements: [RESPValueConvertible], into key: String) -> EventLoopFuture<Int> {
assert(elements.count > 0, "At least 1 element should be provided.")
return send(command: "RPUSH", with: [key] + elements)
.mapFromRESP()
}
/// Inserts the value at the head of the list only if the key exists and holds a list.
/// - Note: This inserts the values at the tail of the list, for the head see `lpushx(_:to:)`.
/// Pushes an element into a list, but only if the key exists and holds a list.
/// - Note: This inserts the element at the tail of the list; for the head see `lpushx(_:into:)`.
///
/// See [https://redis.io/commands/rpushx](https://redis.io/commands/rpushx)
/// - Parameters:
/// - element: The value to try and push into the list.
/// - key: The key of the list.
/// - Returns: The length of the list after adding the new elements.
@inlinable
public func rpushx(_ value: RESPValueConvertible, to key: String) -> EventLoopFuture<Int> {
return send(command: "RPUSHX", with: [key, value])
public func rpushx(_ element: RESPValueConvertible, into key: String) -> EventLoopFuture<Int> {
return send(command: "RPUSHX", with: [key, element])
.mapFromRESP()
}
}
+193 -99
View File
@@ -1,156 +1,250 @@
import Foundation
import NIO
// MARK: General
extension RedisCommandExecutor {
/// Returns the all of the elements of the set stored at key.
///
/// Ordering of results are stable between multiple calls of this method to the same set.
/// Gets all of the elements contained in a set.
/// - Note: Ordering of results are stable between multiple calls of this method to the same set.
///
/// Results are **UNSTABLE** in regards to the ordering of insertions through the `sadd` command and this method.
///
/// [https://redis.io/commands/smembers](https://redis.io/commands/smembers)
public func smembers(_ key: String) -> EventLoopFuture<RESPValue> {
/// See [https://redis.io/commands/smembers](https://redis.io/commands/smembers)
/// - Parameter key: The key of the set.
/// - Returns: A list of elements found within the set.
@inlinable
public func smembers(of key: String) -> EventLoopFuture<[RESPValue]> {
return send(command: "SMEMBERS", with: [key])
.mapFromRESP()
}
/// Checks if the provided item is included in the set stored at key.
/// Checks if the element is included in a set.
///
/// https://redis.io/commands/sismember
/// - Parameter item: The element to look in the set for, stored as a `bulkString`.
public func sismember(_ key: String, item: RESPValueConvertible) -> EventLoopFuture<Bool> {
return send(command: "SISMEMBER", with: [key, item])
/// See [https://redis.io/commands/sismember](https://redis.io/commands/sismember)
/// - Parameters:
/// - element: The element to look for in the set.
/// - key: The key of the set to look in.
/// - Returns: `true` if the element is in the set.
@inlinable
public func sismember(_ element: RESPValueConvertible, of key: String) -> EventLoopFuture<Bool> {
return send(command: "SISMEMBER", with: [key, element])
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
/// Returns the total count of elements in the set stored at key.
/// Gets the total count of elements within a set.
///
/// [https://redis.io/commands/scard](https://redis.io/commands/scard)
public func scard(_ key: String) -> EventLoopFuture<Int> {
/// See [https://redis.io/commands/scard](https://redis.io/commands/scard)
/// - Parameter key: The key of the set.
/// - Returns: The total count of elements in the set.
@inlinable
public func scard(of key: String) -> EventLoopFuture<Int> {
return send(command: "SCARD", with: [key])
.mapFromRESP()
}
/// Adds the provided items to the set stored at key, returning the count of items added.
/// Adds elements to a set.
///
/// [https://redis.io/commands/sadd](https://redis.io/commands/sadd)
/// - Parameter items: The elements to add to the set, stored as `bulkString`s.
public func sadd(_ key: String, items: [RESPValueConvertible]) -> EventLoopFuture<Int> {
assert(items.count > 0, "There must be at least 1 item to add.")
/// See [https://redis.io/commands/sadd](https://redis.io/commands/sadd)
/// - Parameters:
/// - elements: The values to add to the set.
/// - key: The key of the set to insert into.
/// - Returns: The number of elements that were added to the set.
@inlinable
public func sadd(_ elements: [RESPValueConvertible], to key: String) -> EventLoopFuture<Int> {
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "SADD", with: [key] + items)
return send(command: "SADD", with: [key] + elements)
.mapFromRESP()
}
/// Removes the provided items from the set stored at key, returning the count of items removed.
/// Removes elements from a set.
///
/// [https://redis.io/commands/srem](https://redis.io/commands/srem)
/// - Parameter items: The elemnts to remove from the set, stored as `bulkString`s.
public func srem(_ key: String, items: [RESPValueConvertible]) -> EventLoopFuture<Int> {
assert(items.count > 0, "There must be at least 1 item listed to remove.")
/// See [https://redis.io/commands/srem](https://redis.io/commands/srem)
/// - Parameters:
/// - elements: The values to remove from the set.
/// - key: The key of the set to remove from.
/// - Returns: The number of elements that were removed from the set.
@inlinable
public func srem(_ elements: [RESPValueConvertible], from key: String) -> EventLoopFuture<Int> {
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "SREM", with: [key] + items)
return send(command: "SREM", with: [key] + elements)
.mapFromRESP()
}
/// Randomly selects an item from the set stored at key, and removes it.
/// Randomly selects and removes one or more elements in a set.
///
/// [https://redis.io/commands/spop](https://redis.io/commands/spop)
public func spop(_ key: String) -> EventLoopFuture<RESPValue> {
return send(command: "SPOP", with: [key])
/// See [https://redis.io/commands/spop](https://redis.io/commands/spop)
/// - Parameters:
/// - key: The key of the set.
/// - count: The max number of elements to pop from the set.
/// - Returns: The element that was popped from the set.
@inlinable
public func spop(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
assert(count >= 0, "A negative max count is nonsense.")
guard count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SPOP", with: [key, count])
.mapFromRESP()
}
/// Randomly selects elements from the set stored at string, up to the `count` provided.
/// Use the `RESPValue.array` property to access the underlying values.
/// Randomly selects one or more elements in a set.
///
/// connection.srandmember("my_key") // pulls just one random element
/// connection.srandmember("my_key", max: -3) // pulls up to 3 elements, allowing duplicates
/// connection.srandmember("my_key", max: 3) // pulls up to 3 elements, guaranteed unique
///
/// [https://redis.io/commands/srandmember](https://redis.io/commands/srandmember)
public func srandmember(_ key: String, max count: Int = 1) -> EventLoopFuture<RESPValue> {
assert(count != 0, "A count of zero is a noop for selecting a random element.")
/// See [https://redis.io/commands/srandmember](https://redis.io/commands/srandmember)
/// - Parameters:
/// - key: The key of the set.
/// - count: The max number of elements to select from the set.
/// - Returns: The elements randomly selected from the set.
@inlinable
public func srandmember(from key: String, max count: Int = 1) -> EventLoopFuture<[RESPValue]> {
guard count != 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SRANDMEMBER", with: [key, count.description])
}
/// Returns the members of the set resulting from the difference between the first set and all the successive sets.
///
/// [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
public func sdiff(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
return send(command: "SDIFF", with: keys)
return send(command: "SRANDMEMBER", with: [key, count])
.mapFromRESP()
}
/// Functionally equivalent to `sdiff`, but instead stores the resulting set at the `destination` key
/// and returns the count of elements in the result set.
/// Moves an element from one set to another.
///
/// [https://redis.io/commands/sdiffstore](https://redis.io/commands/sdiffstore)
/// - Important: If the `destination` key already exists, it is overwritten.
public func sdiffstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SDIFFSTORE", with: [dest] + keys)
.mapFromRESP()
}
/// See [https://redis.io/commands/smove](https://redis.io/commands/smove)
/// - Parameters:
/// - element: The value to move from the source.
/// - sourceKey: The key of the source set.
/// - destKey: The key of the destination set.
/// - Returns: `true` if the element was successfully removed from the source set.
@inlinable
public func smove(
_ element: RESPValueConvertible,
from sourceKey: String,
to destKey: String
) -> EventLoopFuture<Bool> {
guard sourceKey != destKey else { return self.eventLoop.makeSucceededFuture(true) }
/// Returns the members of the set resulting from the intersection of all the given sets.
///
/// [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
public func sinter(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
return send(command: "SINTER", with: keys)
.mapFromRESP()
}
/// Functionally equivalent to `sinter`, but instead stores the resulting set at the `destination` key
/// and returns the count of elements in the result set.
///
/// [https://redis.io/commands/sinterstore](https://redis.io/commands/sinterstore)
/// - Important: If the `destination` key already exists, it is overwritten.
public func sinterstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SINTERSTORE", with: [dest] + keys)
.mapFromRESP()
}
/// Moves the `item` from the source key to the destination key.
///
/// [https://redis.io/commands/smove](https://redis.io/commands/smove)
/// - Important: This will resolve to `true` as long as it was successfully removed from the `source` key.
public func smove(item: RESPValueConvertible, fromKey source: String, toKey dest: String) -> EventLoopFuture<Bool> {
return send(command: "SMOVE", with: [source, dest, item])
return send(command: "SMOVE", with: [sourceKey, destKey, element])
.mapFromRESP()
.map { return $0 == 1 }
}
/// Returns the members of the set resulting from the union of all the given keys.
/// Incrementally iterates over all values in a set.
///
/// [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
public func sunion(_ keys: String...) -> EventLoopFuture<[RESPValue]> {
/// See [https://redis.io/commands/sscan](https://redis.io/commands/sscan)
/// - Parameters:
/// - key: The key of the set.
/// - position: The position to start the scan from.
/// - count: The number of elements to advance by. Redis default is 10.
/// - match: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of elements found in the set.
@inlinable
public func sscan(
_ key: String,
startingFrom position: Int = 0,
count: Int? = nil,
matching match: String? = nil
) -> EventLoopFuture<(Int, [RESPValue])> {
return _scan(command: "SSCAN", key, position, count, match)
}
}
// MARK: Diff
extension RedisCommandExecutor {
/// Calculates the difference between two or more sets.
///
/// See [https://redis.io/commands/sdiff](https://redis.io/commands/sdiff)
/// - Parameter keys: The source sets to calculate the difference of.
/// - Returns: A list of elements resulting from the difference.
@inlinable
public func sdiff(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SDIFF", with: keys)
.mapFromRESP()
}
/// Calculates the difference between two or more sets and stores the result.
/// - Important: If the destination key already exists, it is overwritten.
///
/// See [https://redis.io/commands/sdiffstore](https://redis.io/commands/sdiffstore)
/// - Parameters:
/// - destination: The key of the new set from the result.
/// - sources: The list of source sets to calculate the difference of.
/// - Returns: The number of elements in the difference result.
@inlinable
public func sdiffstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SDIFFSTORE", with: [destination] + keys)
.mapFromRESP()
}
}
// MARK: Intersect
extension RedisCommandExecutor {
/// Calculates the intersection of two or more sets.
///
/// See [https://redis.io/commands/sinter](https://redis.io/commands/sinter)
/// - Parameter keys: The source sets to calculate the intersection of.
/// - Returns: A list of elements resulting from the intersection.
@inlinable
public func sinter(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SINTER", with: keys)
.mapFromRESP()
}
/// Calculates the intersetion of two or more sets and stores the result.
/// - Important: If the destination key already exists, it is overwritten.
///
/// See [https://redis.io/commands/sinterstore](https://redis.io/commands/sinterstore)
/// - Parameters:
/// - destination: The key of the new set from the result.
/// - sources: A list of source sets to calculate the intersection of.
/// - Returns: The number of elements in the intersection result.
@inlinable
public func sinterstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SINTERSTORE", with: [destination] + keys)
.mapFromRESP()
}
}
// MARK: Union
extension RedisCommandExecutor {
/// Calculates the union of two or more sets.
///
/// See [https://redis.io/commands/sunion](https://redis.io/commands/sunion)
/// - Parameter keys: The source sets to calculate the union of.
/// - Returns: A list of elements resulting from the union.
@inlinable
public func sunion(of keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "SUNION", with: keys)
.mapFromRESP()
}
/// Functionally equivalent to `sunion`, but instead stores the resulting set at the `destination` key
/// and returns the count of elements in the result set.
/// Calculates the union of two or more sets and stores the result.
/// - Important: If the destination key already exists, it is overwritten.
///
/// [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore)
/// - Important: If the `destination` key already exists, it is overwritten.
public func sunionstore(destination dest: String, _ keys: String...) -> EventLoopFuture<Int> {
return send(command: "SUNIONSTORE", with: [dest] + keys)
/// See [https://redis.io/commands/sunionstore](https://redis.io/commands/sunionstore)
/// - Parameters:
/// - destination: The key of the new set from the result.
/// - sources: A list of source sets to calculate the union of.
/// - Returns: The number of elements in the union result.
@inlinable
public func sunionstore(as destination: String, sources keys: [String]) -> EventLoopFuture<Int> {
assert(keys.count > 0, "At least 1 key should be provided.")
return send(command: "SUNIONSTORE", with: [destination] + keys)
.mapFromRESP()
}
/// Incrementally iterates over all values in a set.
///
/// [https://redis.io/commands/sscan](https://redis.io/commands/sscan)
/// - Parameters:
/// - count: The number of elements to advance by. Redis default is 10.
/// - matching: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of values stored at the keys.
public func sscan(
_ key: String,
atPosition pos: Int = 0,
count: Int? = nil,
matching match: String? = nil) -> EventLoopFuture<(Int, [RESPValue])>
{
return _scan(command: "SSCAN", resultType: [RESPValue].self, key, pos, count, match)
}
}
+203 -169
View File
@@ -4,7 +4,10 @@ import NIO
extension RedisCommandExecutor {
@usableFromInline
static func _mapSortedSetResponse(_ response: [RESPValue], scoreIsFirst: Bool) throws -> [(RESPValue, Double)] {
static func _mapSortedSetResponse(
_ response: [RESPValue],
scoreIsFirst: Bool
) throws -> [(RESPValue, Double)] {
guard response.count > 0 else { return [] }
var result: [(RESPValue, Double)] = []
@@ -17,8 +20,8 @@ extension RedisCommandExecutor {
throw RedisError(identifier: #function, reason: "Unexpected response \"\(scoreItem)\"")
}
let memberIndex = scoreIsFirst ? index + 1 : index
result.append((response[memberIndex], score))
let elementIndex = scoreIsFirst ? index + 1 : index
result.append((response[elementIndex], score))
index += 2
} while (index < response.count)
@@ -34,18 +37,21 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
/// - Parameters:
/// - items: A list of elements and their score to add to the sorted set.
/// - to: The key of the sorted set.
/// - elements: A list of elements and their score to add to the sorted set.
/// - key: The key of the sorted set.
/// - options: A set of options defined by Redis for this command to execute under.
/// - Returns: The number of elements added to the sorted set.
@inlinable
public func zadd(
_ items: [(element: RESPValueConvertible, score: Double)],
_ elements: [(element: RESPValueConvertible, score: Double)],
to key: String,
options: Set<String> = []) -> EventLoopFuture<Int>
{
options: Set<String> = []
) -> EventLoopFuture<Int> {
guard !options.contains("INCR") else {
return eventLoop.makeFailedFuture(RedisError(identifier: #function, reason: "INCR option is unsupported. Use zincrby(_:member:by:) instead."))
return self.eventLoop.makeFailedFuture(RedisError(
identifier: #function,
reason: "INCR option is unsupported. Use zincrby(_:element:in:) instead."
))
}
assert(options.count <= 2, "Invalid number of options provided.")
@@ -57,13 +63,8 @@ extension RedisCommandExecutor {
var args: [RESPValueConvertible] = [key] + options.map { $0 }
for (element, score) in items {
switch score {
case .infinity: args.append("+inf")
case -.infinity: args.append("-inf")
default: args.append(score)
}
for (element, score) in elements {
args.append(score)
args.append(element)
}
@@ -75,24 +76,24 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
/// - Parameters:
/// - item: The element and its score to add to the sorted set.
/// - to: The key of the sorted set.
/// - element: The element and its score to add to the sorted set.
/// - key: The key of the sorted set.
/// - options: A set of options defined by Redis for this command to execute under.
/// - Returns: `true` if the element was added or score was updated in the sorted set.
@inlinable
public func zadd(
_ item: (element: RESPValueConvertible, score: Double),
_ element: (element: RESPValueConvertible, score: Double),
to key: String,
options: Set<String> = []) -> EventLoopFuture<Bool>
{
return zadd([item], to: key, options: options)
options: Set<String> = []
) -> EventLoopFuture<Bool> {
return zadd([element], to: key, options: options)
.map { return $0 == 1 }
}
/// Returns the number of elements in a sorted set.
/// Gets the number of elements in a sorted set.
///
/// See [https://redis.io/commands/zcard](https://redis.io/commands/zcard)
/// - Parameter of: The key of the sorted set.
/// - Parameter key: The key of the sorted set.
/// - Returns: The number of elements in the sorted set.
@inlinable
public func zcard(of key: String) -> EventLoopFuture<Int> {
@@ -100,35 +101,35 @@ extension RedisCommandExecutor {
.mapFromRESP()
}
/// Returns the score of the specified member in a stored set.
/// Gets the score of the specified element in a stored set.
///
/// See [https://redis.io/commands/zscore](https://redis.io/commands/zscore)
/// - Parameters:
/// - of: The element in the sorted set to get the score for.
/// - storedAt: The key of the sorted set.
/// - Returns: The score of the element provided.
/// - element: The element in the sorted set to get the score for.
/// - key: The key of the sorted set.
/// - Returns: The score of the element provided, or `nil` if the element is not found in the set or the set does not exist.
@inlinable
public func zscore(of member: RESPValueConvertible, storedAt key: String) -> EventLoopFuture<Double?> {
return send(command: "ZSCORE", with: [key, member])
public func zscore(of element: RESPValueConvertible, in key: String) -> EventLoopFuture<Double?> {
return send(command: "ZSCORE", with: [key, element])
.map { return Double($0) }
}
/// Incrementally iterates over all fields in a sorted set.
/// Incrementally iterates over all elements in a sorted set.
///
/// See [https://redis.io/commands/zscan](https://redis.io/commands/zscan)
/// - Parameters:
/// - key: The key identifying the sorted set.
/// - startingFrom: The position to start the scan from.
/// - position: The position to start the scan from.
/// - count: The number of elements to advance by. Redis default is 10.
/// - matching: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of values and their scores.
/// - match: A glob-style pattern to filter values to be selected from the result set.
/// - Returns: A cursor position for additional invocations with a limited collection of elements found in the sorted set with their scores.
@inlinable
public func zscan(
_ key: String,
startingFrom position: Int = 0,
count: Int? = nil,
matching match: String? = nil) -> EventLoopFuture<(Int, [(RESPValue, Double)])>
{
matching match: String? = nil
) -> EventLoopFuture<(Int, [(RESPValue, Double)])> {
return _scan(command: "ZSCAN", resultType: [RESPValue].self, key, position, count, match)
.flatMapThrowing {
let values = try Self._mapSortedSetResponse($0.1, scoreIsFirst: false)
@@ -142,31 +143,31 @@ extension RedisCommandExecutor {
extension RedisCommandExecutor {
/// Returns the rank (index) of the specified element in a sorted set.
/// - Note: This treats the ordered set as ordered from low to high.
/// For the inverse, see `zrevrank(of:storedAt:)`.
/// For the inverse, see `zrevrank(of:in:)`.
///
/// See [https://redis.io/commands/zrank](https://redis.io/commands/zrank)
/// - Parameters:
/// - of: The element in the sorted set to search for.
/// - storedAt: The key of the sorted set to search.
/// - element: The element in the sorted set to search for.
/// - key: The key of the sorted set to search.
/// - Returns: The index of the element, or `nil` if the key was not found.
@inlinable
public func zrank(of member: RESPValueConvertible, storedAt key: String) -> EventLoopFuture<Int?> {
return send(command: "ZRANK", with: [key, member])
public func zrank(of element: RESPValueConvertible, in key: String) -> EventLoopFuture<Int?> {
return send(command: "ZRANK", with: [key, element])
.mapFromRESP()
}
/// Returns the rank (index) of the specified element in a sorted set.
/// - Note: This treats the ordered set as ordered from high to low.
/// For the inverse, see `zrank(of:storedAt:)`.
/// For the inverse, see `zrank(of:in:)`.
///
/// See [https://redis.io/commands/zrevrank](https://redis.io/commands/zrevrank)
/// - Parameters:
/// - of: The element in the sorted set to search for.
/// - storedAt: The key of the sorted set to search.
/// - element: The element in the sorted set to search for.
/// - key: The key of the sorted set to search.
/// - Returns: The index of the element, or `nil` if the key was not found.
@inlinable
public func zrevrank(of member: RESPValueConvertible, storedAt key: String) -> EventLoopFuture<Int?> {
return send(command: "ZREVRANK", with: [key, member])
public func zrevrank(of element: RESPValueConvertible, in key: String) -> EventLoopFuture<Int?> {
return send(command: "ZREVRANK", with: [key, element])
.mapFromRESP()
}
}
@@ -178,11 +179,14 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
/// - Parameters:
/// - of: The key of the sorted set to count.
/// - within: The min and max range of scores to filter for.
/// - key: The key of the sorted set to count.
/// - range: The min and max range of scores to filter for.
/// - Returns: The number of elements in the sorted set that fit within the score range.
@inlinable
public func zcount(of key: String, within range: (min: String, max: String)) -> EventLoopFuture<Int> {
public func zcount(
of key: String,
within range: (min: String, max: String)
) -> EventLoopFuture<Int> {
return send(command: "ZCOUNT", with: [key, range.min, range.max])
.mapFromRESP()
}
@@ -192,11 +196,14 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zlexcount](https://redis.io/commands/zlexcount)
/// - Parameters:
/// - of: The key of the sorted set to count.
/// - within: The min and max range of values to filter for.
/// - key: The key of the sorted set to count.
/// - range: The min and max range of values to filter for.
/// - Returns: The number of elements in the sorted set that fit within the value range.
@inlinable
public func zlexcount(of key: String, within range: (min: String, max: String)) -> EventLoopFuture<Int> {
public func zlexcount(
of key: String,
within range: (min: String, max: String)
) -> EventLoopFuture<Int> {
return send(command: "ZLEXCOUNT", with: [key, range.min, range.max])
.mapFromRESP()
}
@@ -205,23 +212,22 @@ extension RedisCommandExecutor {
// MARK: Pop
extension RedisCommandExecutor {
/// Removes members from a sorted set with the lowest scores.
/// Removes elements from a sorted set with the lowest scores.
///
/// See [https://redis.io/commands/zpopmin](https://redis.io/commands/zpopmin)
/// - Parameters:
/// - key: The key identifying the sorted set in Redis.
/// - count: The max number of elements to pop from the set.
/// - from: The key identifying the sorted set in Redis.
/// - Returns: A list of members popped from the sorted set with their associated score.
/// - Returns: A list of elements popped from the sorted set with their associated score.
@inlinable
public func zpopmin(_ count: Int, from key: String) -> EventLoopFuture<[(RESPValue, Double)]> {
public func zpopmin(from key: String, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
return _zpop(command: "ZPOPMIN", count, key)
}
/// Removes a member from a sorted set with the lowest score.
/// Removes the element from a sorted set with the lowest score.
///
/// See [https://redis.io/commands/zpopmin](https://redis.io/commands/zpopmin)
/// - Parameters:
/// - from: The key identifying the sorted set in Redis.
/// - Parameter key: The key identifying the sorted set in Redis.
/// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty.
@inlinable
public func zpopmin(from key: String) -> EventLoopFuture<(RESPValue, Double)?> {
@@ -229,23 +235,22 @@ extension RedisCommandExecutor {
.map { return $0.count > 0 ? $0[0] : nil }
}
/// Removes members from a sorted set with the highest scores.
/// Removes elements from a sorted set with the highest scores.
///
/// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
/// - Parameters:
/// - key: The key identifying the sorted set in Redis.
/// - count: The max number of elements to pop from the set.
/// - from: The key identifying the sorted set in Redis.
/// - Returns: A list of members popped from the sorted set with their associated score.
/// - Returns: A list of elements popped from the sorted set with their associated score.
@inlinable
public func zpopmax(_ count: Int, from key: String) -> EventLoopFuture<[(RESPValue, Double)]> {
public func zpopmax(from key: String, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
return _zpop(command: "ZPOPMAX", count, key)
}
/// Removes a member from a sorted set with the highest score.
/// Removes the element from a sorted set with the highest score.
///
/// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
/// - Parameters:
/// - from: The key identifying the sorted set in Redis.
/// - Parameter key: The key identifying the sorted set in Redis.
/// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty.
@inlinable
public func zpopmax(from key: String) -> EventLoopFuture<(RESPValue, Double)?> {
@@ -254,10 +259,18 @@ extension RedisCommandExecutor {
}
@usableFromInline
func _zpop(command: String, _ count: Int?, _ key: String) -> EventLoopFuture<[(RESPValue, Double)]> {
func _zpop(
command: String,
_ count: Int?,
_ key: String
) -> EventLoopFuture<[(RESPValue, Double)]> {
var args: [RESPValueConvertible] = [key]
if let c = count { args.append(c) }
if let c = count {
guard c != 0 else { return self.eventLoop.makeSucceededFuture([]) }
args.append(c)
}
return send(command: command, with: args)
.mapFromRESP(to: [RESPValue].self)
@@ -268,31 +281,21 @@ extension RedisCommandExecutor {
// MARK: Increment
extension RedisCommandExecutor {
/// Increments the score of the specified member in a sorted set.
/// Increments the score of the specified element in a sorted set.
///
/// See [https://redis.io/commands/zincrby](https://redis.io/commands/zincrby)
/// - Parameters:
/// - amount: The amount to increment this element's score by.
/// - element: The element to increment.
/// - key: The key of the sorted set.
/// - member: The element to increment.
/// - by: The amount to increment this element's score by.
/// - Returns: The new score of the member.
/// - Returns: The new score of the element.
@inlinable
public func zincrby(_ key: String, member: RESPValueConvertible, by amount: Int) -> EventLoopFuture<Double> {
return send(command: "ZINCRBY", with: [key, amount, member])
.mapFromRESP()
}
/// Increments the score of the specified member in a sorted set.
///
/// See [https://redis.io/commands/zincrby](https://redis.io/commands/zincrby)
/// - Parameters:
/// - key: The key of the sorted set.
/// - member: The element to increment.
/// - by: The amount to increment this element's score by.
/// - Returns: The new score of the member.
@inlinable
public func zincrby(_ key: String, member: RESPValueConvertible, by amount: Double) -> EventLoopFuture<Double> {
return send(command: "ZINCRBY", with: [key, amount, member])
public func zincrby(
_ amount: Double,
element: RESPValueConvertible,
in key: String
) -> EventLoopFuture<Double> {
return send(command: "ZINCRBY", with: [key, amount, element])
.mapFromRESP()
}
}
@@ -300,43 +303,43 @@ extension RedisCommandExecutor {
// MARK: Intersect and Union
extension RedisCommandExecutor {
/// Computes a new sorted set as a union between all provided source sorted sets and stores the result at the key desired.
/// Calculates the union of two or more sorted sets and stores the result.
/// - Note: This operation overwrites any value stored at the destination key.
///
/// See [https://redis.io/commands/zunionstore](https://redis.io/commands/zunionstore)
/// - Parameters:
/// - destination: The key of the new sorted set from the result.
/// - sources: The list of sorted set keys to treat as the source of the union.
/// - to: The key to store the union sorted set at.
/// - weights: The multiplying factor to apply to the corresponding `sources` key based on index of the two parameters.
/// - aggregateMethod: The method of aggregating the values of the union. Supported values are "SUM", "MIN", and "MAX".
/// - Returns: The number of members in the new sorted set.
/// - Returns: The number of elements in the new sorted set.
@inlinable
public func zunionstore(
_ sources: [String],
to destination: String,
as destination: String,
sources: [String],
weights: [Int]? = nil,
aggregateMethod aggregate: String? = nil) -> EventLoopFuture<Int>
{
aggregateMethod aggregate: String? = nil
) -> EventLoopFuture<Int> {
return _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate)
}
/// Computes a new sorted set as an intersection between all provided source sorted sets and stores the result at the key desired.
/// Calculates the intersection of two or more sorted sets and stores the result.
/// - Note: This operation overwrites any value stored at the destination key.
///
/// See [https://redis.io/commands/zinterstore](https://redis.io/commands/zinterstore)
/// - Parameters:
/// - destination: The key of the new sorted set from the result.
/// - sources: The list of sorted set keys to treat as the source of the intersection.
/// - to: The key to store the intersected sorted set at.
/// - weights: The multiplying factor to apply to the corresponding `sources` key based on index of the two parameters.
/// - aggregateMethod: The method of aggregating the values of the intersection. Supported values are "SUM", "MIN", and "MAX".
/// - Returns: The number of members in the new sorted set.
/// - Returns: The number of elements in the new sorted set.
@inlinable
public func zinterstore(
_ sources: [String],
to destination: String,
as destination: String,
sources: [String],
weights: [Int]? = nil,
aggregateMethod aggregate: String? = nil) -> EventLoopFuture<Int>
{
aggregateMethod aggregate: String? = nil
) -> EventLoopFuture<Int> {
return _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate)
}
@@ -375,46 +378,54 @@ extension RedisCommandExecutor {
// MARK: Range
extension RedisCommandExecutor {
/// Returns the specified range of elements in a sorted set.
/// Gets the specified range of elements in a sorted set.
/// - Note: This treats the ordered set as ordered from low to high.
/// For the inverse, see `zrevrange(of:startIndex:endIndex:withScores:)`.
///
/// For the inverse, see `zrevrange(within:from:withScores:)`.
///
/// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
/// - Parameters:
/// - withinIndices: The start and stop 0-based indices of the range of elements to include.
/// - from: The key of the sorted set to search.
/// - withScores: Should the list contain the items AND their scores? [Item_1, Score_1, Item_2, ...]
/// - range: The start and stop 0-based indices of the range of elements to include.
/// - key: The key of the sorted set to search.
/// - withScores: Should the list contain the elements AND their scores? [Item_1, Score_1, Item_2, ...]
/// - Returns: A list of elements from the sorted set that were within the range provided, and optionally their scores.
@inlinable
public func zrange(
withinIndices range: (start: Int, stop: Int),
within range: (start: Int, stop: Int),
from key: String,
withScores: Bool = false) -> EventLoopFuture<[RESPValue]>
{
withScores: Bool = false
) -> EventLoopFuture<[RESPValue]> {
return _zrange(command: "ZRANGE", key, range.start, range.stop, withScores)
}
/// Returns the specified range of elements in a sorted set.
/// Gets the specified range of elements in a sorted set.
/// - Note: This treats the ordered set as ordered from high to low.
/// For the inverse, see `zrange(of:startIndex:endIndex:withScores:)`.
///
/// For the inverse, see `zrange(within:from:withScores:)`.
///
/// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
/// - Parameters:
/// - withinIndices: The start and stop 0-based indices of the range of elements to include.
/// - from: The key of the sorted set to search.
/// - withScores: Should the list contain the items AND their scores? [Item_1, Score_1, Item_2, ...]
/// - range: The start and stop 0-based indices of the range of elements to include.
/// - key: The key of the sorted set to search.
/// - withScores: Should the list contain the elements AND their scores? [Item_1, Score_1, Item_2, ...]
/// - Returns: A list of elements from the sorted set that were within the range provided, and optionally their scores.
@inlinable
public func zrevrange(
withinIndices range: (start: Int, stop: Int),
within range: (start: Int, stop: Int),
from key: String,
withScores: Bool = false) -> EventLoopFuture<[RESPValue]>
{
withScores: Bool = false
) -> EventLoopFuture<[RESPValue]> {
return _zrange(command: "ZREVRANGE", key, range.start, range.stop, withScores)
}
@usableFromInline
func _zrange(command: String, _ key: String, _ start: Int, _ stop: Int, _ withScores: Bool) -> EventLoopFuture<[RESPValue]> {
func _zrange(
command: String,
_ key: String,
_ start: Int,
_ stop: Int,
_ withScores: Bool
) -> EventLoopFuture<[RESPValue]> {
var args: [RESPValueConvertible] = [key, start, stop]
if withScores { args.append("WITHSCORES") }
@@ -427,50 +438,58 @@ extension RedisCommandExecutor {
// MARK: Range by Score
extension RedisCommandExecutor {
/// Returns elements from a sorted set whose score fits within the range specified.
/// Gets elements from a sorted set whose score fits within the range specified.
/// - Note: This treats the ordered set as ordered from low to high.
/// For the inverse, see `zrevrangebyscore(of:within:withScores:limitBy:)`.
///
/// For the inverse, see `zrevrangebyscore(within:from:withScores:limitBy:)`.
///
/// See [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore)
/// - Parameters:
/// - within: The range of min and max scores to filter elements by.
/// - from: The key of the sorted set to search.
/// - withScores: Should the list contain the items AND their scores? [Item_1, Score_1, Item_2, ...]
/// - limitBy: The optional offset and count of items to query.
/// - range: The range of min and max scores to filter elements by.
/// - key: The key of the sorted set to search.
/// - withScores: Should the list contain the elements AND their scores? [Item_1, Score_1, Item_2, ...]
/// - limit: The optional offset and count of elements to query.
/// - Returns: A list of elements from the sorted set that were within the range provided, and optionally their scores.
@inlinable
public func zrangebyscore(
within range: (min: String, max: String),
from key: String,
withScores: Bool = false,
limitBy limit: (offset: Int, count: Int)? = nil) -> EventLoopFuture<[RESPValue]>
{
limitBy limit: (offset: Int, count: Int)? = nil
) -> EventLoopFuture<[RESPValue]> {
return _zrangebyscore(command: "ZRANGEBYSCORE", key, range, withScores, limit)
}
/// Returns elements from a sorted set whose score fits within the range specified.
/// Gets elements from a sorted set whose score fits within the range specified.
/// - Note: This treats the ordered set as ordered from high to low.
/// For the inverse, see `zrangebyscore(of:within:withScores:limitBy:)`.
///
/// For the inverse, see `zrangebyscore(within:from:withScores:limitBy:)`.
///
/// See [https://redis.io/commands/zrevrangebyscore](https://redis.io/commands/zrevrangebyscore)
/// - Parameters:
/// - within: The range of min and max scores to filter elements by.
/// - from: The key of the sorted set to search.
/// - withScores: Should the list contain the items AND their scores? [Item_1, Score_1, Item_2, ...]
/// - limitBy: The optional offset and count of items to query.
/// - range: The range of min and max scores to filter elements by.
/// - key: The key of the sorted set to search.
/// - withScores: Should the list contain the elements AND their scores? [Item_1, Score_1, Item_2, ...]
/// - limit: The optional offset and count of elements to query.
/// - Returns: A list of elements from the sorted set that were within the range provided, and optionally their scores.
@inlinable
public func zrevrangebyscore(
within range: (min: String, max: String),
from key: String,
withScores: Bool = false,
limitBy limit: (offset: Int, count: Int)? = nil) -> EventLoopFuture<[RESPValue]>
{
limitBy limit: (offset: Int, count: Int)? = nil
) -> EventLoopFuture<[RESPValue]> {
return _zrangebyscore(command: "ZREVRANGEBYSCORE", key, (range.max, range.min), withScores, limit)
}
@usableFromInline
func _zrangebyscore(command: String, _ key: String, _ range: (min: String, max: String), _ withScores: Bool, _ limit: (offset: Int, count: Int)?) -> EventLoopFuture<[RESPValue]> {
func _zrangebyscore(
command: String,
_ key: String,
_ range: (min: String, max: String),
_ withScores: Bool,
_ limit: (offset: Int, count: Int)?
) -> EventLoopFuture<[RESPValue]> {
var args: [RESPValueConvertible] = [key, range.min, range.max]
if withScores { args.append("WITHSCORES") }
@@ -488,48 +507,55 @@ extension RedisCommandExecutor {
// MARK: Range by Lexiographical
extension RedisCommandExecutor {
/// Returns elements from a sorted set whose lexiographical values are between the range specified.
/// Gets elements from a sorted set whose lexiographical values are between the range specified.
/// - Important: This assumes all elements in the sorted set have the same score. If not, the returned elements are unspecified.
/// - Note: This treats the ordered set as ordered from low to high.
/// For the inverse, see `zrevrangebylex(of:within:limitBy:)`.
///
/// For the inverse, see `zrevrangebylex(within:from:limitBy:)`.
///
/// See [https://redis.io/commands/zrangebylex](https://redis.io/commands/zrangebylex)
/// - Parameters:
/// - within: The value range to filter elements by.
/// - from: The key of the sorted set to search.
/// - limitBy: The optional offset and count of items to query.
/// - range: The value range to filter elements by.
/// - key: The key of the sorted set to search.
/// - limit: The optional offset and count of elements to query.
/// - Returns: A list of elements from the sorted set that were within the range provided.
@inlinable
public func zrangebylex(
within range: (min: String, max: String),
from key: String,
limitBy limit: (offset: Int, count: Int)? = nil) -> EventLoopFuture<[RESPValue]>
{
limitBy limit: (offset: Int, count: Int)? = nil
) -> EventLoopFuture<[RESPValue]> {
return _zrangebylex(command: "ZRANGEBYLEX", key, range, limit)
}
/// Returns elements from a sorted set whose lexiographical values are between the range specified.
/// Gets elements from a sorted set whose lexiographical values are between the range specified.
/// - Important: This assumes all elements in the sorted set have the same score. If not, the returned elements are unspecified.
/// - Note: This treats the ordered set as ordered from high to low.
/// For the inverse, see `zrangebylex(of:within:limitBy:)`.
///
/// For the inverse, see `zrangebylex(within:from:limitBy:)`.
///
/// See [https://redis.io/commands/zrevrangebylex](https://redis.io/commands/zrevrangebylex)
/// - Parameters:
/// - within: The value range to filter elements by.
/// - from: The key of the sorted set to search.
/// - limitBy: The optional offset and count of items to query.
/// - range: The value range to filter elements by.
/// - key: The key of the sorted set to search.
/// - limit: The optional offset and count of elements to query.
/// - Returns: A list of elements from the sorted set that were within the range provided.
@inlinable
public func zrevrangebylex(
within range: (min: String, max: String),
from key: String,
limitBy limit: (offset: Int, count: Int)? = nil) -> EventLoopFuture<[RESPValue]>
{
limitBy limit: (offset: Int, count: Int)? = nil
) -> EventLoopFuture<[RESPValue]> {
return _zrangebylex(command: "ZREVRANGEBYLEX", key, (range.max, range.min), limit)
}
@usableFromInline
func _zrangebylex(command: String, _ key: String, _ range: (min: String, max: String), _ limit: (offset: Int, count: Int)?) -> EventLoopFuture<[RESPValue]> {
func _zrangebylex(
command: String,
_ key: String,
_ range: (min: String, max: String),
_ limit: (offset: Int, count: Int)?
) -> EventLoopFuture<[RESPValue]> {
var args: [RESPValueConvertible] = [key, range.min, range.max]
if let l = limit {
@@ -545,18 +571,18 @@ extension RedisCommandExecutor {
// MARK: Remove
extension RedisCommandExecutor {
/// Removes the specified items from a sorted set.
/// Removes the specified elements from a sorted set.
///
/// See [https://redis.io/commands/zrem](https://redis.io/commands/zrem)
/// - Parameters:
/// - items: The values to remove from the sorted set.
/// - from: The key of the sorted set.
/// - Returns: The number of items removed from the set.
/// - elements: The values to remove from the sorted set.
/// - key: The key of the sorted set.
/// - Returns: The number of elements removed from the set.
@inlinable
public func zrem(_ items: [RESPValueConvertible], from key: String) -> EventLoopFuture<Int> {
assert(items.count > 0, "At least 1 item should be provided.")
public func zrem(_ elements: [RESPValueConvertible], from key: String) -> EventLoopFuture<Int> {
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
return send(command: "ZREM", with: [key] + items)
return send(command: "ZREM", with: [key] + elements)
.mapFromRESP()
}
@@ -565,11 +591,14 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zremrangebylex](https://redis.io/commands/zremrangebylex)
/// - Parameters:
/// - within: The value range to filter for elements to remove.
/// - from: The key of the sorted set to search.
/// - range: The value range to filter for elements to remove.
/// - key: The key of the sorted set to search.
/// - Returns: The number of elements removed from the sorted set.
@inlinable
public func zremrangebylex(within range: (min: String, max: String), from key: String) -> EventLoopFuture<Int> {
public func zremrangebylex(
within range: (min: String, max: String),
from key: String
) -> EventLoopFuture<Int> {
return send(command: "ZREMRANGEBYLEX", with: [key, range.min, range.max])
.mapFromRESP()
}
@@ -578,13 +607,15 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank)
/// - Parameters:
/// - startingFrom: The starting index of the range.
/// - endingAt: The ending index of the range.
/// - from: The key of the sorted set to search.
/// - range: The index range of elements to remove.
/// - key: The key of the sorted set to search.
/// - Returns: The number of elements removed from the sorted set.
@inlinable
public func zremrangebyrank(startingFrom start: Int, endingAt stop: Int, from key: String) -> EventLoopFuture<Int> {
return send(command: "ZREMRANGEBYRANK", with: [key, start, stop])
public func zremrangebyrank(
within range: (start: Int, stop: Int),
from key: String
) -> EventLoopFuture<Int> {
return send(command: "ZREMRANGEBYRANK", with: [key, range.start, range.stop])
.mapFromRESP()
}
@@ -592,11 +623,14 @@ extension RedisCommandExecutor {
///
/// See [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore)
/// - Parameters:
/// - within: The score range to filter for elements to remove.
/// - from: The key of the sorted set to search.
/// - range: The score range to filter for elements to remove.
/// - key: The key of the sorted set to search.
/// - Returns: The number of elements removed from the sorted set.
@inlinable
public func zremrangebyscore(within range: (min: String, max: String), from key: String) -> EventLoopFuture<Int> {
public func zremrangebyscore(
within range: (min: String, max: String),
from key: String
) -> EventLoopFuture<Int> {
return send(command: "ZREMRANGEBYSCORE", with: [key, range.min, range.max])
.mapFromRESP()
}
@@ -0,0 +1,162 @@
import NIO
// MARK: Get
extension RedisCommandExecutor {
/// Get the value of a key.
/// - Note: This operation only works with string values.
/// The `EventLoopFuture` will fail with a `RedisError` if the value is not a string, such as a Set.
///
/// [https://redis.io/commands/get](https://redis.io/commands/get)
/// - Parameter key: The key to fetch the value from.
/// - Returns: The string value stored at the key provided, otherwise `nil` if the key does not exist.
@inlinable
public func get(_ key: String) -> EventLoopFuture<String?> {
return send(command: "GET", with: [key])
.map { return $0.string }
}
/// Gets the values of all specified keys, using `.null` to represent non-existant values.
///
/// See [https://redis.io/commands/mget](https://redis.io/commands/mget)
/// - Parameter keys: The list of keys to fetch the values from.
/// - Returns: The values stored at the keys provided, matching the same order.
@inlinable
public func mget(_ keys: [String]) -> EventLoopFuture<[RESPValue]> {
guard keys.count > 0 else { return self.eventLoop.makeSucceededFuture([]) }
return send(command: "MGET", with: keys)
.mapFromRESP()
}
}
// MARK: Set
extension RedisCommandExecutor {
/// Sets the value stored in the key provided, overwriting the previous value.
///
/// Any previous expiration set on the key is discarded if the SET operation was successful.
///
/// - Important: Regardless of the type of value stored at the key, it will be overwritten to a string value.
///
/// [https://redis.io/commands/set](https://redis.io/commands/set)
/// - Parameters:
/// - key: The key to use to uniquely identify this value.
/// - value: The value to set the key to.
/// - Returns: An `EventLoopFuture` that resolves if the operation was successful.
@inlinable
public func set(_ key: String, to value: RESPValueConvertible) -> EventLoopFuture<Void> {
return send(command: "SET", with: [key, value])
.map { _ in () }
}
/// Sets each key to their 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)
/// - Parameter operations: The key-value list of SET operations to execute.
/// - Returns: An `EventLoopFuture` that resolves if the operation was successful.
@inlinable
public func mset(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Void> {
return _mset(command: "MSET", operations)
.map { _ in () }
}
/// Sets each key to their respective new value, only if all keys do not currently exist.
/// - Note: Use `mset(_:)` if you don't care about overwriting values.
///
/// See [https://redis.io/commands/msetnx](https://redis.io/commands/msetnx)
/// - Parameter operations: The key-value list of SET operations to execute.
/// - Returns: `true` if the operation successfully completed.
@inlinable
public func msetnx(_ operations: [String: RESPValueConvertible]) -> EventLoopFuture<Bool> {
return _mset(command: "MSETNX", operations)
.mapFromRESP(to: Int.self)
.map { return $0 == 1 }
}
@usableFromInline
func _mset(
command: String,
_ operations: [String: RESPValueConvertible]
) -> EventLoopFuture<RESPValue> {
assert(operations.count > 0, "At least 1 key-value pair should be provided.")
let args: [RESPValueConvertible] = operations.reduce(into: [], { (result, element) in
result.append(element.key)
result.append(element.value)
})
return send(command: command, with: args)
}
}
// MARK: Increment
extension RedisCommandExecutor {
/// Increments the stored value by 1.
///
/// See [https://redis.io/commands/incr](https://redis.io/commands/incr)
/// - Parameter key: The key whose value should be incremented.
/// - Returns: The new value after the operation.
@inlinable
public func increment(_ key: String) -> EventLoopFuture<Int> {
return send(command: "INCR", with: [key])
.mapFromRESP()
}
/// Increments the stored value by the amount desired .
///
/// See [https://redis.io/commands/incrby](https://redis.io/commands/incrby)
/// - Parameters:
/// - key: The key whose value should be incremented.
/// - count: The amount that this value should be incremented, supporting both positive and negative values.
/// - Returns: The new value after the operation.
@inlinable
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.
///
/// See [https://redis.io/commands/incrbyfloat](https://redis.io/commands/incrbyfloat)
/// - Parameters:
/// - key: The key whose value should be incremented.
/// - count: The amount that this value should be incremented, supporting both positive and negative values.
/// - Returns: The new value after the operation.
@inlinable
public func increment<T: BinaryFloatingPoint>(_ key: String, by count: T) -> EventLoopFuture<T>
where T: RESPValueConvertible
{
return send(command: "INCRBYFLOAT", with: [key, count])
.mapFromRESP()
}
}
// MARK: Decrement
extension RedisCommandExecutor {
/// Decrements the stored value by 1.
///
/// See [https://redis.io/commands/decr](https://redis.io/commands/decr)
/// - Parameter key: The key whose value should be decremented.
/// - Returns: The new value after the operation.
@inlinable
public func decrement(_ key: String) -> EventLoopFuture<Int> {
return send(command: "DECR", with: [key])
.mapFromRESP()
}
/// Decrements the stored valye by the amount desired.
///
/// See [https://redis.io/commands/decrby](https://redis.io/commands/decrby)
/// - Parameters:
/// - key: The key whose value should be decremented.
/// - count: The amount that this value should be decremented, supporting both positive and negative values.
/// - 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()
}
}