mirror of
https://github.com/swift-server/RediStack.git
synced 2026-05-03 07:32:28 +00:00
0ecb3c1ef3
Motivation: Issue #60 called for improving the type safety of the options available for the `zadd` command, and MR !70 made some great headway, but attempted to cram too much into a single enum. Modifications: - Break the `RedisSortedSetAddOption.returnChangedCount` value into an additional boolean param Result: Using `zadd` should now be more straight forward, while being type safe.
886 lines
36 KiB
Swift
886 lines
36 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the RediStack open source project
|
|
//
|
|
// Copyright (c) 2019 RediStack project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of RediStack project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import NIO
|
|
|
|
// MARK: Static Helpers
|
|
|
|
extension RedisClient {
|
|
@usableFromInline
|
|
static func _mapSortedSetResponse(
|
|
_ response: [RESPValue],
|
|
scoreIsFirst: Bool
|
|
) throws -> [(RESPValue, Double)] {
|
|
guard response.count > 0 else { return [] }
|
|
|
|
var result: [(RESPValue, Double)] = []
|
|
|
|
var index = 0
|
|
repeat {
|
|
let scoreItem = response[scoreIsFirst ? index : index + 1]
|
|
|
|
guard let score = Double(fromRESP: scoreItem) else {
|
|
throw RedisClientError.assertionFailure(message: "Unexpected response: '\(scoreItem)'")
|
|
}
|
|
|
|
let elementIndex = scoreIsFirst ? index + 1 : index
|
|
result.append((response[elementIndex], score))
|
|
|
|
index += 2
|
|
} while (index < response.count)
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
// MARK: General
|
|
|
|
/// The supported options for the `zadd` command with Redis SortedSet types.
|
|
///
|
|
/// See [https://redis.io/commands/zadd#zadd-options-redis-302-or-greater](https://redis.io/commands/zadd#zadd-options-redis-302-or-greater)
|
|
public enum RedisSortedSetAddOption: String {
|
|
/// When adding elements, any that do not already exist in the SortedSet will be ignored and the score of the existing element will be updated.
|
|
case onlyUpdateExistingElements = "XX"
|
|
/// When adding elements, any that already exist in the SortedSet will be ignored and the score of the existing element will not be updated.
|
|
case onlyAddNewElements = "NX"
|
|
}
|
|
|
|
extension RedisClient {
|
|
/// Adds elements to a sorted set, assigning their score to the values provided.
|
|
/// - Note: `INCR` is not supported by this library in `zadd`. Use the `zincrby(:element:in:)` method instead.
|
|
///
|
|
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
|
|
/// - Parameters:
|
|
/// - elements: A list of elements and their score to add to the sorted set.
|
|
/// - key: The key of the sorted set.
|
|
/// - option: An option for modifying the behavior of the command.
|
|
/// - returnChangedCount: `zadd` normally returns the number of new elements added to the set,
|
|
/// but setting this to `true` will instead have the command return the number of elements changed.
|
|
///
|
|
/// "Changed" in this context are new elements added, and elements that had their score updated.
|
|
/// - Returns: The number of elements added to the sorted set, unless `returnChangedCount` was set to `true`.
|
|
@inlinable
|
|
public func zadd<Value: RESPValueConvertible>(
|
|
_ elements: [(element: Value, score: Double)],
|
|
to key: String,
|
|
option: RedisSortedSetAddOption? = nil,
|
|
returnChangedCount: Bool = false
|
|
) -> EventLoopFuture<Int> {
|
|
var args: [RESPValue] = [.init(bulk: key)]
|
|
|
|
if let opt = option {
|
|
args.append(.init(bulk: opt.rawValue))
|
|
}
|
|
if returnChangedCount {
|
|
args.append(.init(bulk: "CH"))
|
|
}
|
|
args.add(contentsOf: elements, overestimatedCountBeingAdded: elements.count * 2) { (array, next) in
|
|
array.append(.init(bulk: next.score.description))
|
|
array.append(next.element.convertedToRESPValue())
|
|
}
|
|
|
|
return send(command: "ZADD", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Adds an element to a sorted set, assigning their score to the value provided.
|
|
///
|
|
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
|
|
/// - Parameters:
|
|
/// - element: The element and its score to add to the sorted set.
|
|
/// - key: The key of the sorted set.
|
|
/// - option: An option for modifying the behavior of the command.
|
|
/// - returnChangedCount: `zadd` normally returns the number of new elements added to the set,
|
|
/// but setting this to `true` will instead have the command return the number of elements changed.
|
|
///
|
|
/// "Changed" in this context are new elements added, and elements that had their score updated.
|
|
/// - Returns: `true` if the element was added or score was updated in the sorted set, depending on the `option` and `returnChangedCount` settings set.
|
|
@inlinable
|
|
public func zadd<Value: RESPValueConvertible>(
|
|
_ element: (element: Value, score: Double),
|
|
to key: String,
|
|
option: RedisSortedSetAddOption? = nil,
|
|
returnChangedCount: Bool = false
|
|
) -> EventLoopFuture<Bool> {
|
|
return zadd([element], to: key, option: option, returnChangedCount: returnChangedCount)
|
|
.map { return $0 == 1 }
|
|
}
|
|
|
|
/// Gets the number of elements in a sorted set.
|
|
///
|
|
/// See [https://redis.io/commands/zcard](https://redis.io/commands/zcard)
|
|
/// - 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> {
|
|
let args = [RESPValue(bulk: key)]
|
|
return send(command: "ZCARD", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Gets the score of the specified element in a stored set.
|
|
///
|
|
/// See [https://redis.io/commands/zscore](https://redis.io/commands/zscore)
|
|
/// - Parameters:
|
|
/// - 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<Value: RESPValueConvertible>(of element: Value, in key: String) -> EventLoopFuture<Double?> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
element.convertedToRESPValue()
|
|
]
|
|
return send(command: "ZSCORE", with: args)
|
|
.map { return Double(fromRESP: $0) }
|
|
}
|
|
|
|
/// 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.
|
|
/// - 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 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)])> {
|
|
return _scan(command: "ZSCAN", resultType: [RESPValue].self, key, position, count, match)
|
|
.flatMapThrowing {
|
|
let values = try Self._mapSortedSetResponse($0.1, scoreIsFirst: false)
|
|
return ($0.0, values)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Rank
|
|
|
|
extension RedisClient {
|
|
/// 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:in:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrank](https://redis.io/commands/zrank)
|
|
/// - Parameters:
|
|
/// - 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<Value: RESPValueConvertible>(of element: Value, in key: String) -> EventLoopFuture<Int?> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
element.convertedToRESPValue()
|
|
]
|
|
return send(command: "ZRANK", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// 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:in:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrevrank](https://redis.io/commands/zrevrank)
|
|
/// - Parameters:
|
|
/// - 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<Value: RESPValueConvertible>(of element: Value, in key: String) -> EventLoopFuture<Int?> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
element.convertedToRESPValue()
|
|
]
|
|
return send(command: "ZREVRANK", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Count
|
|
|
|
extension RedisClient {
|
|
/// Returns the number of elements in a sorted set with a score within the range specified.
|
|
///
|
|
/// See [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
|
|
/// - Parameters:
|
|
/// - 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> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
return send(command: "ZCOUNT", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Returns the number of elements in 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.
|
|
///
|
|
/// See [https://redis.io/commands/zlexcount](https://redis.io/commands/zlexcount)
|
|
/// - Parameters:
|
|
/// - 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> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
return send(command: "ZLEXCOUNT", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Pop
|
|
|
|
extension RedisClient {
|
|
/// 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.
|
|
/// - Returns: A list of elements popped from the sorted set with their associated score.
|
|
@inlinable
|
|
public func zpopmin(from key: String, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
|
|
return _zpop(command: "ZPOPMIN", count, key)
|
|
}
|
|
|
|
/// Removes the element from a sorted set with the lowest score.
|
|
///
|
|
/// See [https://redis.io/commands/zpopmin](https://redis.io/commands/zpopmin)
|
|
/// - 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)?> {
|
|
return _zpop(command: "ZPOPMIN", nil, key)
|
|
.map { return $0.count > 0 ? $0[0] : nil }
|
|
}
|
|
|
|
/// 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.
|
|
/// - Returns: A list of elements popped from the sorted set with their associated score.
|
|
@inlinable
|
|
public func zpopmax(from key: String, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
|
|
return _zpop(command: "ZPOPMAX", count, key)
|
|
}
|
|
|
|
/// Removes the element from a sorted set with the highest score.
|
|
///
|
|
/// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
|
|
/// - 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)?> {
|
|
return _zpop(command: "ZPOPMAX", nil, key)
|
|
.map { return $0.count > 0 ? $0[0] : nil }
|
|
}
|
|
|
|
@usableFromInline
|
|
func _zpop(
|
|
command: String,
|
|
_ count: Int?,
|
|
_ key: String
|
|
) -> EventLoopFuture<[(RESPValue, Double)]> {
|
|
var args: [RESPValue] = [.init(bulk: key)]
|
|
|
|
if let c = count {
|
|
guard c != 0 else { return self.eventLoop.makeSucceededFuture([]) }
|
|
|
|
args.append(.init(bulk: c))
|
|
}
|
|
|
|
return send(command: command, with: args)
|
|
.convertFromRESPValue(to: [RESPValue].self)
|
|
.flatMapThrowing { return try Self._mapSortedSetResponse($0, scoreIsFirst: true) }
|
|
}
|
|
}
|
|
|
|
// MARK: Blocking Pop
|
|
|
|
extension RedisClient {
|
|
/// Removes the element from a sorted set with the lowest score, blocking until an element is
|
|
/// available.
|
|
///
|
|
/// - Important:
|
|
/// This will block the connection from completing further commands until an element
|
|
/// is available to pop from the set.
|
|
///
|
|
/// It is **highly** recommended to set a reasonable `timeout`
|
|
/// or to use the non-blocking `zpopmin` method where possible.
|
|
///
|
|
/// See [https://redis.io/commands/bzpopmin](https://redis.io/commands/bzpopmin)
|
|
/// - Parameters:
|
|
/// - key: The key identifying the sorted set in Redis.
|
|
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
|
/// - Returns:
|
|
/// The element and its associated score that was popped from the sorted set,
|
|
/// or `nil` if the timeout was reached.
|
|
@inlinable
|
|
public func bzpopmin(
|
|
from key: String,
|
|
timeout: Int = 0
|
|
) -> EventLoopFuture<(Double, RESPValue)?> {
|
|
return bzpopmin(from: [key], timeout: timeout)
|
|
.map {
|
|
guard let response = $0 else { return nil }
|
|
return (response.1, response.2)
|
|
}
|
|
}
|
|
|
|
/// Removes the element from a sorted set with the lowest score, blocking until an element is
|
|
/// available.
|
|
///
|
|
/// - Important:
|
|
/// This will block the connection from completing further commands until an element
|
|
/// is available to pop from the group of sets.
|
|
///
|
|
/// It is **highly** recommended to set a reasonable `timeout`
|
|
/// or to use the non-blocking `zpopmin` method where possible.
|
|
///
|
|
/// See [https://redis.io/commands/bzpopmin](https://redis.io/commands/bzpopmin)
|
|
/// - Parameters:
|
|
/// - keys: A list of sorted set keys in Redis.
|
|
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
|
/// - Returns:
|
|
/// If timeout was reached, `nil`.
|
|
///
|
|
/// Otherwise, the key of the sorted set the element was removed from, the element itself,
|
|
/// and its associated score is returned.
|
|
@inlinable
|
|
public func bzpopmin(
|
|
from keys: [String],
|
|
timeout: Int = 0
|
|
) -> EventLoopFuture<(String, Double, RESPValue)?> {
|
|
return self._bzpop(command: "BZPOPMIN", keys, timeout)
|
|
}
|
|
|
|
/// Removes the element from a sorted set with the highest score, blocking until an element is
|
|
/// available.
|
|
///
|
|
/// - Important:
|
|
/// This will block the connection from completing further commands until an element
|
|
/// is available to pop from the set.
|
|
///
|
|
/// It is **highly** recommended to set a reasonable `timeout`
|
|
/// or to use the non-blocking `zpopmax` method where possible.
|
|
///
|
|
/// See [https://redis.io/commands/bzpopmax](https://redis.io/commands/bzpopmax)
|
|
/// - Parameters:
|
|
/// - key: The key identifying the sorted set in Redis.
|
|
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
|
/// - Returns:
|
|
/// The element and its associated score that was popped from the sorted set,
|
|
/// or `nil` if the timeout was reached.
|
|
@inlinable
|
|
public func bzpopmax(
|
|
from key: String,
|
|
timeout: Int = 0
|
|
) -> EventLoopFuture<(Double, RESPValue)?> {
|
|
return self.bzpopmax(from: [key], timeout: timeout)
|
|
.map {
|
|
guard let response = $0 else { return nil }
|
|
return (response.1, response.2)
|
|
}
|
|
}
|
|
|
|
/// Removes the element from a sorted set with the highest score, blocking until an element is
|
|
/// available.
|
|
///
|
|
/// - Important:
|
|
/// This will block the connection from completing further commands until an element
|
|
/// is available to pop from the group of sets.
|
|
///
|
|
/// It is **highly** recommended to set a reasonable `timeout`
|
|
/// or to use the non-blocking `zpopmax` method where possible.
|
|
///
|
|
/// See [https://redis.io/commands/bzpopmax](https://redis.io/commands/bzpopmax)
|
|
/// - Parameters:
|
|
/// - keys: A list of sorted set keys in Redis.
|
|
/// - timeout: The time (in seconds) to wait. `0` means indefinitely.
|
|
/// - Returns:
|
|
/// If timeout was reached, `nil`.
|
|
///
|
|
/// Otherwise, the key of the sorted set the element was removed from, the element itself,
|
|
/// and its associated score is returned.
|
|
@inlinable
|
|
public func bzpopmax(
|
|
from keys: [String],
|
|
timeout: Int = 0
|
|
) -> EventLoopFuture<(String, Double, RESPValue)?> {
|
|
return self._bzpop(command: "BZPOPMAX", keys, timeout)
|
|
}
|
|
|
|
@usableFromInline
|
|
func _bzpop(
|
|
command: String,
|
|
_ keys: [String],
|
|
_ timeout: Int
|
|
) -> EventLoopFuture<(String, Double, RESPValue)?> {
|
|
var args = keys.map(RESPValue.init)
|
|
args.append(.init(bulk: timeout))
|
|
|
|
return send(command: command, with: args)
|
|
// per the Redis docs,
|
|
// we will receive either a nil response,
|
|
// or an array with 3 elements in the form [Set Key, Element Score, Element Value]
|
|
.flatMapThrowing {
|
|
guard !$0.isNull else { return nil }
|
|
guard let response = [RESPValue](fromRESP: $0) else {
|
|
throw RedisClientError.failedRESPConversion(to: [RESPValue].self)
|
|
}
|
|
assert(response.count == 3, "Unexpected response size returned!")
|
|
guard
|
|
let key = response[0].string,
|
|
let score = Double(fromRESP: response[1])
|
|
else {
|
|
throw RedisClientError.assertionFailure(message: "Unexpected structure in response: \(response)")
|
|
}
|
|
return (key, score, response[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Increment
|
|
|
|
extension RedisClient {
|
|
/// 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.
|
|
/// - Returns: The new score of the element.
|
|
@inlinable
|
|
public func zincrby<Value: RESPValueConvertible>(
|
|
_ amount: Double,
|
|
element: Value,
|
|
in key: String
|
|
) -> EventLoopFuture<Double> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: amount.description),
|
|
element.convertedToRESPValue()
|
|
]
|
|
return send(command: "ZINCRBY", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Intersect and Union
|
|
|
|
/// The supported methods for aggregating results from the `zunionstore` or `zinterstore` commands in Redis.
|
|
///
|
|
/// For more information on these values, see
|
|
/// [https://redis.io/commands/zunionstore](https://redis.io/commands/zunionstore)
|
|
/// [https://redis.io/commands/zinterstore](https://redis.io/commands/zinterstore)
|
|
public enum RedisSortedSetAggregateMethod: String {
|
|
/// Add the score of all matching elements in the source SortedSets.
|
|
case sum = "SUM"
|
|
/// Use the minimum score of the matching elements in the source SortedSets.
|
|
case min = "MIN"
|
|
/// Use the maximum score of the matching elements in the source SortedSets.
|
|
case max = "MAX"
|
|
}
|
|
|
|
extension RedisClient {
|
|
/// 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.
|
|
/// - 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. If one isn't specified, Redis will default to `.sum`.
|
|
/// - Returns: The number of elements in the new sorted set.
|
|
@inlinable
|
|
public func zunionstore(
|
|
as destination: String,
|
|
sources: [String],
|
|
weights: [Int]? = nil,
|
|
aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
|
|
) -> EventLoopFuture<Int> {
|
|
return _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate)
|
|
}
|
|
|
|
/// 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.
|
|
/// - 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. If one isn't specified, Redis will default to `.sum`.
|
|
/// - Returns: The number of elements in the new sorted set.
|
|
@inlinable
|
|
public func zinterstore(
|
|
as destination: String,
|
|
sources: [String],
|
|
weights: [Int]? = nil,
|
|
aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
|
|
) -> EventLoopFuture<Int> {
|
|
return _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate)
|
|
}
|
|
|
|
@usableFromInline
|
|
func _zopstore(
|
|
command: String,
|
|
_ sources: [String],
|
|
_ destination: String,
|
|
_ weights: [Int]?,
|
|
_ aggregate: RedisSortedSetAggregateMethod?
|
|
) -> EventLoopFuture<Int> {
|
|
assert(sources.count > 0, "At least 1 source key should be provided.")
|
|
|
|
var args: [RESPValue] = [
|
|
.init(bulk: destination),
|
|
.init(bulk: sources.count)
|
|
]
|
|
args.append(convertingContentsOf: sources)
|
|
|
|
if let w = weights {
|
|
assert(w.count > 0, "When passing a value for 'weights', at least 1 value should be provided.")
|
|
assert(w.count <= sources.count, "Weights should be no larger than the amount of source keys.")
|
|
|
|
args.append(.init(bulk: "WEIGHTS"))
|
|
args.append(convertingContentsOf: w)
|
|
}
|
|
|
|
if let a = aggregate {
|
|
args.append(.init(bulk: "AGGREGATE"))
|
|
args.append(.init(bulk: a.rawValue))
|
|
}
|
|
|
|
return send(command: command, with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Range
|
|
|
|
extension RedisClient {
|
|
/// 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(within:from:withScores:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
|
|
/// - Parameters:
|
|
/// - 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(
|
|
within range: (start: Int, stop: Int),
|
|
from key: String,
|
|
withScores: Bool = false
|
|
) -> EventLoopFuture<[RESPValue]> {
|
|
return _zrange(command: "ZRANGE", key, range.start, range.stop, withScores)
|
|
}
|
|
|
|
/// 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(within:from:withScores:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
|
|
/// - Parameters:
|
|
/// - 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(
|
|
within range: (start: Int, stop: Int),
|
|
from key: String,
|
|
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]> {
|
|
var args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: start),
|
|
.init(bulk: stop)
|
|
]
|
|
|
|
if withScores { args.append(.init(bulk: "WITHSCORES")) }
|
|
|
|
return send(command: command, with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Range by Score
|
|
|
|
extension RedisClient {
|
|
/// 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(within:from:withScores:limitBy:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrangebyscore](https://redis.io/commands/zrangebyscore)
|
|
/// - Parameters:
|
|
/// - 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]> {
|
|
return _zrangebyscore(command: "ZRANGEBYSCORE", key, range, withScores, limit)
|
|
}
|
|
|
|
/// 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(within:from:withScores:limitBy:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrevrangebyscore](https://redis.io/commands/zrevrangebyscore)
|
|
/// - Parameters:
|
|
/// - 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]> {
|
|
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]> {
|
|
var args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
|
|
if withScores { args.append(.init(bulk: "WITHSCORES")) }
|
|
|
|
if let l = limit {
|
|
args.append(.init(bulk: "LIMIT"))
|
|
args.append(convertingContentsOf: [l.offset, l.count])
|
|
}
|
|
|
|
return send(command: command, with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Range by Lexiographical
|
|
|
|
extension RedisClient {
|
|
/// 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(within:from:limitBy:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrangebylex](https://redis.io/commands/zrangebylex)
|
|
/// - Parameters:
|
|
/// - 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]> {
|
|
return _zrangebylex(command: "ZRANGEBYLEX", key, range, limit)
|
|
}
|
|
|
|
/// 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(within:from:limitBy:)`.
|
|
///
|
|
/// See [https://redis.io/commands/zrevrangebylex](https://redis.io/commands/zrevrangebylex)
|
|
/// - Parameters:
|
|
/// - 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]> {
|
|
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]> {
|
|
var args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
|
|
if let l = limit {
|
|
args.reserveCapacity(6) // 3 above, plus 3 being added
|
|
args.append(.init(bulk: "LIMIT"))
|
|
args.append(.init(bulk: l.offset))
|
|
args.append(.init(bulk: l.count))
|
|
}
|
|
|
|
return send(command: command, with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|
|
|
|
// MARK: Remove
|
|
|
|
extension RedisClient {
|
|
/// Removes the specified elements from a sorted set.
|
|
///
|
|
/// See [https://redis.io/commands/zrem](https://redis.io/commands/zrem)
|
|
/// - Parameters:
|
|
/// - 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<Value: RESPValueConvertible>(_ elements: [Value], from key: String) -> EventLoopFuture<Int> {
|
|
guard elements.count > 0 else { return self.eventLoop.makeSucceededFuture(0) }
|
|
|
|
var args: [RESPValue] = [.init(bulk: key)]
|
|
args.append(convertingContentsOf: elements)
|
|
|
|
return send(command: "ZREM", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Removes 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 elements selected are unspecified.
|
|
///
|
|
/// See [https://redis.io/commands/zremrangebylex](https://redis.io/commands/zremrangebylex)
|
|
/// - Parameters:
|
|
/// - 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> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
return send(command: "ZREMRANGEBYLEX", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Removes elements from a sorted set whose index is between the provided range.
|
|
///
|
|
/// See [https://redis.io/commands/zremrangebyrank](https://redis.io/commands/zremrangebyrank)
|
|
/// - Parameters:
|
|
/// - 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(
|
|
within range: (start: Int, stop: Int),
|
|
from key: String
|
|
) -> EventLoopFuture<Int> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.start),
|
|
.init(bulk: range.stop)
|
|
]
|
|
return send(command: "ZREMRANGEBYRANK", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
|
|
/// Removes elements from a sorted set whose score is within the range specified.
|
|
///
|
|
/// See [https://redis.io/commands/zremrangebyscore](https://redis.io/commands/zremrangebyscore)
|
|
/// - Parameters:
|
|
/// - 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> {
|
|
let args: [RESPValue] = [
|
|
.init(bulk: key),
|
|
.init(bulk: range.min),
|
|
.init(bulk: range.max)
|
|
]
|
|
return send(command: "ZREMRANGEBYSCORE", with: args)
|
|
.convertFromRESPValue()
|
|
}
|
|
}
|