SortedSetCommands.swift 93.5 KB
Newer Older
1 2
//===----------------------------------------------------------------------===//
//
3
// This source file is part of the RediStack open source project
4
//
5
// Copyright (c) 2019 RediStack project authors
6 7 8
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
9
// See CONTRIBUTORS.txt for the list of RediStack project authors
10 11 12 13 14
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

15 16 17 18
import NIO

// MARK: Static Helpers

19
extension RedisClient {
20
    @usableFromInline
Nathan Harris's avatar
Nathan Harris committed
21 22 23 24
    static func _mapSortedSetResponse(
        _ response: [RESPValue],
        scoreIsFirst: Bool
    ) throws -> [(RESPValue, Double)] {
25 26 27 28 29 30 31 32
        guard response.count > 0 else { return [] }

        var result: [(RESPValue, Double)] = []

        var index = 0
        repeat {
            let scoreItem = response[scoreIsFirst ? index : index + 1]

33
            guard let score = Double(fromRESP: scoreItem) else {
34
                throw RedisClientError.assertionFailure(message: "Unexpected response: '\(scoreItem)'")
35 36
            }

Nathan Harris's avatar
Nathan Harris committed
37 38
            let elementIndex = scoreIsFirst ? index + 1 : index
            result.append((response[elementIndex], score))
39 40 41 42 43 44 45 46

            index += 2
        } while (index < response.count)

        return result
    }
}

47
// MARK: Zadd
48

49 50 51 52 53 54 55
/// The supported insert behavior for a `zadd` command with Redis SortedSet types.
///
/// `zadd` normally inserts all elements (`.allElements`) provided into the SortedSet, updating the score of any element that already exist in the set.
///
/// However, it supports two other insert behaviors:
/// * `.onlyNewElements` will not update the score of any element already in the SortedSet
/// * `.onlyExistingElements` will not insert any new element into the SortedSet
56
///
57
/// See [https://redis.io/commands/zadd#zadd-options-redis-302-or-greater](https://redis.io/commands/zadd#zadd-options-redis-302-or-greater)
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
public enum RedisZaddInsertBehavior {
    /// Insert new elements and update the score of existing elements.
    case allElements
    /// Only insert new elements; do not update the score of existing elements.
    case onlyNewElements
    /// Only update the score of existing elements; do not insert new elements.
    case onlyExistingElements
    
    /// Redis representation of this option.
    @usableFromInline
    internal var string: String? {
        switch self {
        case .allElements: return nil
        case .onlyNewElements: return "NX"
        case .onlyExistingElements: return "XX"
        }
    }
}

/// The supported behavior for what a `zadd` command return value should represent.
///
/// `zadd` normally returns the number of new elements inserted into the set (`.insertedElementsCount`),
/// but also supports the option (`.changedElementsCount`) to return the number of elements changed as a result of the command.
///
/// "Changed" in this context refers to both new elements that were inserted and existing elements that had their score updated.
///
/// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
public enum RedisZaddReturnBehavior {
    /// Count both new elements that were inserted into the SortedSet and existing elements that had their score updated.
    case changedElementsCount
    /// Count only new elements that were inserted into the SortedSet.
    case insertedElementsCount
    
    /// Redis representation of this option.
    @usableFromInline
    internal var string: String? {
        switch self {
        case .changedElementsCount: return "CH"
        case .insertedElementsCount: return nil
        }
    }
99 100
}

101
extension RedisClient {
102 103 104 105
    /// Adds elements to a sorted set, assigning their score to the values provided.
    ///
    /// See [https://redis.io/commands/zadd](https://redis.io/commands/zadd)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
106 107
    ///     - elements: A list of elements and their score to add to the sorted set.
    ///     - key: The key of the sorted set.
108 109 110
    ///     - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
    ///     - returnBehavior: The desired behavior of what the return value should represent.
    /// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
111
    @inlinable
112 113
    public func zadd<Value: RESPValueConvertible>(
        _ elements: [(element: Value, score: Double)],
114
        to key: RedisKey,
115 116
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
Nathan Harris's avatar
Nathan Harris committed
117
    ) -> EventLoopFuture<Int> {
118
        var args: [RESPValue] = [.init(bulk: key)]
119
        
120
        args.append(convertingContentsOf: [insertBehavior.string, returnBehavior.string].compactMap({ $0 }))
121 122 123
        args.add(contentsOf: elements, overestimatedCountBeingAdded: elements.count * 2) { (array, next) in
            array.append(.init(bulk: next.score.description))
            array.append(next.element.convertedToRESPValue())
124 125
        }

126
        return self.send(command: "ZADD", with: args)
127
            .map()
128
    }
129 130 131 132 133 134 135
    
    /// Adds elements to a sorted set, assigning their score to the values provided.
    ///
    /// 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.
136 137 138
    ///     - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
    ///     - returnBehavior: The desired behavior of what the return value should represent.
    /// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
139 140 141
    @inlinable
    public func zadd<Value: RESPValueConvertible>(
        _ elements: (element: Value, score: Double)...,
142
        to key: RedisKey,
143 144
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
145
    ) -> EventLoopFuture<Int> {
146
        return self.zadd(elements, to: key, inserting: insertBehavior, returning: returnBehavior)
147
    }
148 149 150 151 152

    /// 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:
Nathan Harris's avatar
Nathan Harris committed
153 154
    ///     - element: The element and its score to add to the sorted set.
    ///     - key: The key of the sorted set.
155 156 157
    ///     - insertBehavior: The desired behavior of handling new and existing elements in the SortedSet.
    ///     - returnBehavior: The desired behavior of what the return value should represent.
    /// - Returns: If `returning` is `.changedElementsCount`, the number of elements inserted and that had their score updated. Otherwise, just the number of new elements inserted.
158
    @inlinable
159 160
    public func zadd<Value: RESPValueConvertible>(
        _ element: (element: Value, score: Double),
161
        to key: RedisKey,
162 163
        inserting insertBehavior: RedisZaddInsertBehavior = .allElements,
        returning returnBehavior: RedisZaddReturnBehavior = .insertedElementsCount
Nathan Harris's avatar
Nathan Harris committed
164
    ) -> EventLoopFuture<Bool> {
165
        return self.zadd(element, to: key, inserting: insertBehavior, returning: returnBehavior)
166 167
            .map { return $0 == 1 }
    }
168
}
169

170 171 172
// MARK: General

extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
173
    /// Gets the number of elements in a sorted set.
174 175
    ///
    /// See [https://redis.io/commands/zcard](https://redis.io/commands/zcard)
Nathan Harris's avatar
Nathan Harris committed
176
    /// - Parameter key: The key of the sorted set.
177 178
    /// - Returns: The number of elements in the sorted set.
    @inlinable
179
    public func zcard(of key: RedisKey) -> EventLoopFuture<Int> {
180 181
        let args = [RESPValue(bulk: key)]
        return send(command: "ZCARD", with: args)
182
            .map()
183 184
    }

Nathan Harris's avatar
Nathan Harris committed
185
    /// Gets the score of the specified element in a stored set.
186 187 188
    ///
    /// See [https://redis.io/commands/zscore](https://redis.io/commands/zscore)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
189 190 191
    ///     - 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.
192
    @inlinable
193
    public func zscore<Value: RESPValueConvertible>(of element: Value, in key: RedisKey) -> EventLoopFuture<Double?> {
194 195 196 197 198
        let args: [RESPValue] = [
            .init(bulk: key),
            element.convertedToRESPValue()
        ]
        return send(command: "ZSCORE", with: args)
199
            .map { return Double(fromRESP: $0) }
200 201
    }

Nathan Harris's avatar
Nathan Harris committed
202
    /// Incrementally iterates over all elements in a sorted set.
203 204 205 206
    ///
    /// See [https://redis.io/commands/zscan](https://redis.io/commands/zscan)
    /// - Parameters:
    ///     - key: The key identifying the sorted set.
Nathan Harris's avatar
Nathan Harris committed
207
    ///     - position: The position to start the scan from.
208
    ///     - count: The number of elements to advance by. Redis default is 10.
Nathan Harris's avatar
Nathan Harris committed
209 210
    ///     - 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.
211 212
    @inlinable
    public func zscan(
213
        _ key: RedisKey,
214 215
        startingFrom position: Int = 0,
        count: Int? = nil,
Nathan Harris's avatar
Nathan Harris committed
216 217
        matching match: String? = nil
    ) -> EventLoopFuture<(Int, [(RESPValue, Double)])> {
218 219 220 221 222 223 224 225 226 227
        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

228
extension RedisClient {
229 230
    /// Returns the rank (index) of the specified element in a sorted set.
    /// - Note: This treats the ordered set as ordered from low to high.
Nathan Harris's avatar
Nathan Harris committed
231
    /// For the inverse, see `zrevrank(of:in:)`.
232 233 234
    ///
    /// See [https://redis.io/commands/zrank](https://redis.io/commands/zrank)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
235 236
    ///     - element: The element in the sorted set to search for.
    ///     - key: The key of the sorted set to search.
237 238
    /// - Returns: The index of the element, or `nil` if the key was not found.
    @inlinable
239
    public func zrank<Value: RESPValueConvertible>(of element: Value, in key: RedisKey) -> EventLoopFuture<Int?> {
240 241 242 243 244
        let args: [RESPValue] = [
            .init(bulk: key),
            element.convertedToRESPValue()
        ]
        return send(command: "ZRANK", with: args)
245
            .map()
246 247 248 249
    }

    /// Returns the rank (index) of the specified element in a sorted set.
    /// - Note: This treats the ordered set as ordered from high to low.
Nathan Harris's avatar
Nathan Harris committed
250
    /// For the inverse, see `zrank(of:in:)`.
251 252 253
    ///
    /// See [https://redis.io/commands/zrevrank](https://redis.io/commands/zrevrank)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
254 255
    ///     - element: The element in the sorted set to search for.
    ///     - key: The key of the sorted set to search.
256 257
    /// - Returns: The index of the element, or `nil` if the key was not found.
    @inlinable
258
    public func zrevrank<Value: RESPValueConvertible>(of element: Value, in key: RedisKey) -> EventLoopFuture<Int?> {
259 260 261 262 263
        let args: [RESPValue] = [
            .init(bulk: key),
            element.convertedToRESPValue()
        ]
        return send(command: "ZREVRANK", with: args)
264
            .map()
265 266 267 268 269
    }
}

// MARK: Count

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
/// Represents a range bound for use with the Redis SortedSet commands related to element scores.
///
/// This type conforms to `ExpressibleByFloatLiteral` and `ExpressibleByIntegerLiteral`, which will initialize to an `.inclusive` bound.
///
/// For example:
/// ```swift
/// let literalBound: RedisZScoreBound = 3 // .inclusive(3)
/// let otherLiteralBound: RedisZScoreBound = 3.0 // .inclusive(3)
/// let exclusiveBound = RedisZScoreBound.exclusive(4)
/// ```
public enum RedisZScoreBound {
    case inclusive(Double)
    case exclusive(Double)
    
    /// The underlying raw score value this bound represents.
    public var rawValue: Double {
        switch self {
        case let .inclusive(v), let .exclusive(v): return v
        }
    }
}

extension RedisZScoreBound: CustomStringConvertible {
    public var description: String {
        switch self {
        case let .inclusive(value): return value.description
        case let .exclusive(value): return "(\(value.description)"
        }
    }
}

extension RedisZScoreBound: ExpressibleByFloatLiteral {
    public typealias FloatLiteralType = Double
    
    public init(floatLiteral value: Double) {
        self = .inclusive(value)
    }
}

extension RedisZScoreBound: ExpressibleByIntegerLiteral {
    public typealias IntegerLiteralType = Int64

    public init(integerLiteral value: Int64) {
        self = .inclusive(Double(value))
    }
}

317
extension RedisClient {
318
    /// Returns the count of elements in a SortedSet with a score within the range specified (inclusive by default).
319
    ///
320 321 322 323 324 325 326 327 328 329 330
    /// To get a count of elements that have at least the score of 3, but no greater than 10:
    /// ```swift
    /// client.zcount(of: "mySortedSet", withScoresBetween: (3, 10))
    /// ```
    ///
    /// To get a count of elements that have at least the score of 3, but less than 10:
    /// ```swift
    /// client.zcount(of: "mySortedSet", withScoresBetween: (3, .exclusive(10)))
    /// ```
    ///
    /// See `RedisZScoreBound` and [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
331
    /// - Parameters:
332 333 334
    ///     - key: The key of the SortedSet that will be counted.
    ///     - range: The min and max score bounds that an element should have in order to be counted.
    /// - Returns: The count of elements in the SortedSet with a score matching the range specified.
335
    @inlinable
Nathan Harris's avatar
Nathan Harris committed
336
    public func zcount(
337
        of key: RedisKey,
338
        withScoresBetween range: (min: RedisZScoreBound, max: RedisZScoreBound)
Nathan Harris's avatar
Nathan Harris committed
339
    ) -> EventLoopFuture<Int> {
340
        guard range.min.rawValue <= range.max.rawValue else { return self.eventLoop.makeSucceededFuture(0) }
341 342
        let args: [RESPValue] = [
            .init(bulk: key),
343 344
            .init(bulk: range.min.description),
            .init(bulk: range.max.description)
345
        ]
346
        return self.send(command: "ZCOUNT", with: args)
347
            .map()
348
    }
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
    
    /// Returns the count of elements in a SortedSet with a score within the inclusive range specified.
    ///
    /// To get a count of elements that have at least the score of 3, but no greater than 10:
    /// ```swift
    /// client.zcount(of: "mySortedSet", withScores: 3...10)
    /// ```
    ///
    /// See [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - range: The inclusive range of scores to filter elements to count.
    /// - Returns: The count of elements in the SortedSet with a score within the range specified.
    @inlinable
    public func zcount(of key: RedisKey, withScores range: ClosedRange<Double>) -> EventLoopFuture<Int> {
        return self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .inclusive(range.upperBound)))
    }
    
    /// Returns the count of elements in a SortedSet with a minimum score up to, but not including, a max score.
    ///
    /// To get a count of elements that have at least the score of 3, but less than 10:
    /// ```swift
    /// client.zcount(of: "mySortedSet", withScores: 3..<10)
    /// ```
    ///
    /// See [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - range: A range with an inclusive lower and exclusive upper bound of scores to filter elements to count.
    /// - Returns: The count of elements in the SortedSet with a score within the range specified.
    @inlinable
    public func zcount(of key: RedisKey, withScores range: Range<Double>) -> EventLoopFuture<Int> {
        return self.zcount(of: key, withScoresBetween: (.inclusive(range.lowerBound), .exclusive(range.upperBound)))
    }
    
    /// Returns the count of elements in a SortedSet whose score is greater than a minimum score value.
    ///
    /// By default, the value provided will be treated as _inclusive_, meaning any element that has a score matching the value **will** be counted.
    ///
    /// See `RedisZScoreBound` and [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - minScore: The minimum score bound an element in the SortedSet should have in order to be counted.
    /// - Returns: The count of elements in the SortedSet above the `minScore` threshold.
    @inlinable
    public func zcount(of key: RedisKey, withMinimumScoreOf minScore: RedisZScoreBound) -> EventLoopFuture<Int> {
        return self.zcount(of: key, withScoresBetween: (minScore, .inclusive(.infinity)))
    }
    
    /// Returns the count of elements in a SortedSet whose score is less than a maximum score value.
    ///
    /// By default, the value provided will be treated as _inclusive_, meaning any element that has a score matching the value **will** be counted.
    ///
    /// See `RedisZScoreBound` and [https://redis.io/commands/zcount](https://redis.io/commands/zcount)
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - maxScore: The maximum score bound an element in the SortedSet should have in order to be counted.
    ///     - exclusive: Should the `maxScore` provided be exclusive? If `true`, scores matching the `maxScore` will **not** be counted.
    /// - Returns: The count of elements in the SortedSet below the `maxScore` threshold.
    @inlinable
    public func zcount(of key: RedisKey, withMaximumScoreOf maxScore: RedisZScoreBound) -> EventLoopFuture<Int> {
        return self.zcount(of: key, withScoresBetween: (.inclusive(-.infinity), maxScore))
    }
}

// MARK: Lexiographical Count

/// Represents a range bound for use with the Redis SortedSet lexiographical commands to compare values.
///
/// Cases must be explicitly declared, with wrapped values conforming to `CustomStringConvertible`.
///
/// The cases `.negativeInfinity` and `.positiveInfinity` represent the special characters in Redis of `-` and `+` respectively.
/// These are constants for absolute lower and upper value bounds that are always treated as _inclusive_.
///
/// See [https://redis.io/commands/zrangebylex#details-on-strings-comparison](https://redis.io/commands/zrangebylex#details-on-strings-comparison)
public enum RedisZLexBound<Value: CustomStringConvertible> {
    case inclusive(Value)
    case exclusive(Value)
    case positiveInfinity
    case negativeInfinity
}

extension RedisZLexBound: CustomStringConvertible {
    public var description: String {
        switch self {
        case let .inclusive(value): return "[\(value)"
        case let .exclusive(value): return "(\(value)"
        case .positiveInfinity: return "+"
        case .negativeInfinity: return "-"
        }
    }
}
441

442 443 444 445 446 447 448 449 450 451 452 453 454
extension RedisZLexBound where Value: BinaryFloatingPoint {
    public var description: String {
        switch self {
        case .inclusive(.infinity), .exclusive(.infinity), .positiveInfinity: return "+"
        case .inclusive(-.infinity), .exclusive(-.infinity), .negativeInfinity: return "-"
        case let .inclusive(value): return "[\(value)"
        case let .exclusive(value): return "(\(value)"
        }
    }
}

extension RedisClient {
    /// Returns the count of elements in a SortedSet whose lexiographical values are between the range specified.
455
    ///
456 457 458 459 460 461 462 463 464
    /// For example:
    /// ```swift
    /// // "mySortedSet" contains the values [1, 2, 3, 10] each with a score of 1.
    /// client.zlexcount(of: "mySortedSet", withValuesBetween: (.inclusive(1), .inclusive(3)))
    /// // the response will resolve to 4, as both 10 and 1 have the value "1"
    /// ```
    ///
    /// See `RedisZLexBound` and [https://redis.io/commands/zlexcount](https://redis.io/commands/zlexcount)
    /// - Warning: This assumes all elements in the SortedSet have the same score. If not, the returned elements are unspecified.
465
    /// - Parameters:
466 467 468
    ///     - key: The key of the SortedSet that will be counted.
    ///     - range: The min and max value bounds that an element should have in order to be counted.
    /// - Returns: The count of elements in the SortedSet with values matching the range specified.
469
    @inlinable
470
    public func zlexcount<Value: CustomStringConvertible>(
471
        of key: RedisKey,
472
        withValuesBetween range: (min: RedisZLexBound<Value>, max: RedisZLexBound<Value>)
Nathan Harris's avatar
Nathan Harris committed
473
    ) -> EventLoopFuture<Int> {
474 475
        let args: [RESPValue] = [
            .init(bulk: key),
476 477
            .init(bulk: range.min.description),
            .init(bulk: range.max.description)
478
        ]
479
        return self.send(command: "ZLEXCOUNT", with: args)
480
            .map()
481
    }
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
    
    /// Returns the count of elements in a SortedSet whose lexiographical value is greater than a minimum value.
    ///
    /// For example with a SortedSet that contains the values [1, 2, 3, 10] and each a score of 1:
    /// ```swift
    /// client.zlexcount(of: "mySortedSet", withMinimumValueOf: .inclusive(2))
    /// // the response will resolve to 2, as "10" lexiographically comes before element "2"
    ///
    /// client.zlexcount(of: "mySortedSet", withMinimumValueOf: .inclusive(10))
    /// // the response will resolve to 3, as the set is ordered as ["1", "10", "2", "3"]
    /// ```
    ///
    /// See `RedisZLexBound` and [https://redis.io/commands/zlexcount](https://redis.io/commands/zlexcount)
    /// - Warning: This assumes all elements in the SortedSet have the same score. If not, the returned elements are unspecified.
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - minValue: The minimum lexiographical value an element in the SortedSet should have in order to be counted.
    /// - Returns: The count of elements in the SortedSet above the `minValue` threshold.
    @inlinable
    public func zlexcount<Value: CustomStringConvertible>(
        of key: RedisKey,
        withMinimumValueOf minValue: RedisZLexBound<Value>
    ) -> EventLoopFuture<Int> {
        return self.zlexcount(of: key, withValuesBetween: (minValue, .positiveInfinity))
    }
    
    /// Returns the count of elements in a SortedSet whose lexiographical value is less than a maximum value.
    ///
    /// For example with a SortedSet that contains the values [1, 2, 3, 10] and each a score of 1:
    /// ```swift
    /// client.zlexcount(of: "mySortedSet", withMaximumValueOf: .exclusive(10))
    /// // the response will resolve to 1, as "1" and "10" are sorted into the first 2 elements
    ///
    /// client.zlexcount(of: "mySortedSet", withMaximumValueOf: .inclusive(3))
    /// // the response will resolve to 4, as the set is ordered as ["1", "10", "2", "3"]
    /// ```
    ///
    /// See `RedisZLexBound` and [https://redis.io/commands/zlexcount](https://redis.io/commands/zlexcount)
    /// - Warning: This assumes all elements in the SortedSet have the same score. If not, the returned elements are unspecified.
    /// - Parameters:
    ///     - key: The key of the SortedSet that will be counted.
    ///     - maxValue: The maximum lexiographical value an element in the SortedSet should have in order to be counted.
    /// - Returns: The count of elements in the SortedSet below the `maxValue` threshold.
    @inlinable
    public func zlexcount<Value: CustomStringConvertible>(
        of key: RedisKey,
        withMaximumValueOf maxValue: RedisZLexBound<Value>
    ) -> EventLoopFuture<Int> {
        return self.zlexcount(of: key, withValuesBetween: (.negativeInfinity, maxValue))
    }
532 533 534 535
}

// MARK: Pop

536
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
537
    /// Removes elements from a sorted set with the lowest scores.
538 539 540
    ///
    /// See [https://redis.io/commands/zpopmin](https://redis.io/commands/zpopmin)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
541
    ///     - key: The key identifying the sorted set in Redis.
542
    ///     - count: The max number of elements to pop from the set.
Nathan Harris's avatar
Nathan Harris committed
543
    /// - Returns: A list of elements popped from the sorted set with their associated score.
544
    @inlinable
545
    public func zpopmin(from key: RedisKey, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
546 547 548
        return _zpop(command: "ZPOPMIN", count, key)
    }

Nathan Harris's avatar
Nathan Harris committed
549
    /// Removes the element from a sorted set with the lowest score.
550 551
    ///
    /// See [https://redis.io/commands/zpopmin](https://redis.io/commands/zpopmin)
Nathan Harris's avatar
Nathan Harris committed
552
    /// - Parameter key: The key identifying the sorted set in Redis.
553 554
    /// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty.
    @inlinable
555
    public func zpopmin(from key: RedisKey) -> EventLoopFuture<(RESPValue, Double)?> {
556 557 558 559
        return _zpop(command: "ZPOPMIN", nil, key)
            .map { return $0.count > 0 ? $0[0] : nil }
    }

Nathan Harris's avatar
Nathan Harris committed
560
    /// Removes elements from a sorted set with the highest scores.
561 562 563
    ///
    /// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
564
    ///     - key: The key identifying the sorted set in Redis.
565
    ///     - count: The max number of elements to pop from the set.
Nathan Harris's avatar
Nathan Harris committed
566
    /// - Returns: A list of elements popped from the sorted set with their associated score.
567
    @inlinable
568
    public func zpopmax(from key: RedisKey, max count: Int) -> EventLoopFuture<[(RESPValue, Double)]> {
569 570 571
        return _zpop(command: "ZPOPMAX", count, key)
    }

Nathan Harris's avatar
Nathan Harris committed
572
    /// Removes the element from a sorted set with the highest score.
573 574
    ///
    /// See [https://redis.io/commands/zpopmax](https://redis.io/commands/zpopmax)
Nathan Harris's avatar
Nathan Harris committed
575
    /// - Parameter key: The key identifying the sorted set in Redis.
576 577
    /// - Returns: The element and its associated score that was popped from the sorted set, or `nil` if set was empty.
    @inlinable
578
    public func zpopmax(from key: RedisKey) -> EventLoopFuture<(RESPValue, Double)?> {
579 580 581 582 583
        return _zpop(command: "ZPOPMAX", nil, key)
            .map { return $0.count > 0 ? $0[0] : nil }
    }

    @usableFromInline
Nathan Harris's avatar
Nathan Harris committed
584 585 586
    func _zpop(
        command: String,
        _ count: Int?,
587
        _ key: RedisKey
Nathan Harris's avatar
Nathan Harris committed
588
    ) -> EventLoopFuture<[(RESPValue, Double)]> {
589
        var args: [RESPValue] = [.init(bulk: key)]
590

Nathan Harris's avatar
Nathan Harris committed
591 592 593
        if let c = count {
            guard c != 0 else { return self.eventLoop.makeSucceededFuture([]) }

594
            args.append(.init(bulk: c))
Nathan Harris's avatar
Nathan Harris committed
595
        }
596 597

        return send(command: command, with: args)
598
            .map(to: [RESPValue].self)
599 600 601 602
            .flatMapThrowing { return try Self._mapSortedSetResponse($0, scoreIsFirst: true) }
    }
}

603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
// 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.
619
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
620 621 622 623 624
    /// - 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(
625
        from key: RedisKey,
626
        timeout: TimeAmount = .seconds(0)
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
    ) -> 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.
648
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
649 650 651 652 653 654 655
    /// - 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(
656
        from keys: [RedisKey],
657
        timeout: TimeAmount = .seconds(0)
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
    ) -> 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.
675
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
676 677 678 679 680
    /// - 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(
681
        from key: RedisKey,
682
        timeout: TimeAmount = .seconds(0)
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703
    ) -> 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.
704
    ///     - timeout: The max time to wait for a value to use. `0`seconds means to wait indefinitely.
705 706 707 708 709 710 711
    /// - 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(
712
        from keys: [RedisKey],
713
        timeout: TimeAmount = .seconds(0)
714 715 716 717 718 719 720
    ) -> EventLoopFuture<(String, Double, RESPValue)?> {
        return self._bzpop(command: "BZPOPMAX", keys, timeout)
    }

    @usableFromInline
    func _bzpop(
        command: String,
721
        _ keys: [RedisKey],
722
        _ timeout: TimeAmount
723
    ) -> EventLoopFuture<(String, Double, RESPValue)?> {
724
        var args = keys.map(RESPValue.init)
725
        args.append(.init(bulk: timeout.seconds))
726
        
727 728 729 730 731 732
        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 }
733
                guard let response = [RESPValue](fromRESP: $0) else {
734
                    throw RedisClientError.failedRESPConversion(to: [RESPValue].self)
735 736 737 738
                }
                assert(response.count == 3, "Unexpected response size returned!")
                guard
                    let key = response[0].string,
739
                    let score = Double(fromRESP: response[1])
740
                else {
741
                    throw RedisClientError.assertionFailure(message: "Unexpected structure in response: \(response)")
742 743 744 745 746 747
                }
                return (key, score, response[2])
            }
    }
}

748 749
// MARK: Increment

750
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
751
    /// Increments the score of the specified element in a sorted set.
752 753 754
    ///
    /// See [https://redis.io/commands/zincrby](https://redis.io/commands/zincrby)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
755 756
    ///     - amount: The amount to increment this element's score by.
    ///     - element: The element to increment.
757
    ///     - key: The key of the sorted set.
Nathan Harris's avatar
Nathan Harris committed
758
    /// - Returns: The new score of the element.
759
    @inlinable
760
    public func zincrby<Value: RESPValueConvertible>(
Nathan Harris's avatar
Nathan Harris committed
761
        _ amount: Double,
762
        element: Value,
763
        in key: RedisKey
Nathan Harris's avatar
Nathan Harris committed
764
    ) -> EventLoopFuture<Double> {
765 766 767 768 769 770
        let args: [RESPValue] = [
            .init(bulk: key),
            .init(bulk: amount.description),
            element.convertedToRESPValue()
        ]
        return send(command: "ZINCRBY", with: args)
771
            .map()
772 773 774 775 776
    }
}

// MARK: Intersect and Union

777 778 779 780 781 782 783 784 785 786 787 788 789 790
/// 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"
}

791
extension RedisClient {
Nathan Harris's avatar
Nathan Harris committed
792
    /// Calculates the union of two or more sorted sets and stores the result.
793 794 795 796
    /// - Note: This operation overwrites any value stored at the destination key.
    ///
    /// See [https://redis.io/commands/zunionstore](https://redis.io/commands/zunionstore)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
797
    ///     - destination: The key of the new sorted set from the result.
798 799
    ///     - 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.
800
    ///     - aggregateMethod: The method of aggregating the values of the union. If one isn't specified, Redis will default to `.sum`.
Nathan Harris's avatar
Nathan Harris committed
801
    /// - Returns: The number of elements in the new sorted set.
802 803
    @inlinable
    public func zunionstore(
804 805
        as destination: RedisKey,
        sources: [RedisKey],
806
        weights: [Int]? = nil,
807
        aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
Nathan Harris's avatar
Nathan Harris committed
808
    ) -> EventLoopFuture<Int> {
809 810 811
        return _zopstore(command: "ZUNIONSTORE", sources, destination, weights, aggregate)
    }

Nathan Harris's avatar
Nathan Harris committed
812
    /// Calculates the intersection of two or more sorted sets and stores the result.
813 814 815 816
    /// - Note: This operation overwrites any value stored at the destination key.
    ///
    /// See [https://redis.io/commands/zinterstore](https://redis.io/commands/zinterstore)
    /// - Parameters:
Nathan Harris's avatar
Nathan Harris committed
817
    ///     - destination: The key of the new sorted set from the result.
818 819
    ///     - 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.
820
    ///     - aggregateMethod: The method of aggregating the values of the intersection. If one isn't specified, Redis will default to `.sum`.
Nathan Harris's avatar
Nathan Harris committed
821
    /// - Returns: The number of elements in the new sorted set.
822 823
    @inlinable
    public func zinterstore(
824 825
        as destination: RedisKey,
        sources: [RedisKey],
826
        weights: [Int]? = nil,
827
        aggregateMethod aggregate: RedisSortedSetAggregateMethod? = nil
Nathan Harris's avatar
Nathan Harris committed
828
    ) -> EventLoopFuture<Int> {
829 830 831 832 833 834
        return _zopstore(command: "ZINTERSTORE", sources, destination, weights, aggregate)
    }

    @usableFromInline
    func _zopstore(
        command: String,
835 836
        _ sources: [RedisKey],
        _ destination: RedisKey,
837
        _ weights: [Int]?,
838 839
        _ aggregate: RedisSortedSetAggregateMethod?
    ) -> EventLoopFuture<Int> {
840 841
        assert(sources.count > 0, "At least 1 source key should be provided.")

842 843 844 845 846
        var args: [RESPValue] = [
            .init(bulk: destination),
            .init(bulk: sources.count)
        ]
        args.append(convertingContentsOf: sources)
847 848 849 850 851

        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.")

852 853
            args.append(.init(bulk: "WEIGHTS"))
            args.append(convertingContentsOf: w)
854 855 856
        }

        if let a = aggregate {
857
            args.append(.init(bulk: "AGGREGATE"))
858
            args.append(.init(bulk: a.rawValue))
859 860 861
        }

        return send(command: command, with: args)
862
            .map()
863 864 865 866 867
    }
}

// MARK: Range

868
extension RedisClient {
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
    /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices.
    ///
    /// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:firstIndex:lastIndex:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet
    ///     - firstIndex: The index of the first element to include in the range of elements returned.
    ///     - lastIndex: The index of the last element to include in the range of elements returned.
    /// - Returns: An array of elements found within the range specified.
    @inlinable
    public func zrange(
        from key: RedisKey,
        firstIndex: Int,
        lastIndex: Int,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self._zrange(command: "ZRANGE", key, firstIndex, lastIndex, includeScores)
    }
    
    /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices.
    ///
    /// To get the elements at index 4 through 7:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: 4...7)
    /// ```
    ///
    /// To get the last 4 elements:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)...(-1))
    /// ```
    ///
    /// To get the first and last 4 elements:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)...3)
    /// ```
Nathan Harris's avatar
Nathan Harris committed
906
    ///
907 908 909 910
    /// To get the first element, and the last 4:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)...0))
    /// ```
911 912
    ///
    /// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
913 914 915 916 917 918 919
    /// - Warning: A `ClosedRange` cannot be created where `upperBound` is less than `lowerBound`; so while Redis may support `0...-1`,
    ///     `ClosedRange` will trigger a precondition failure.
    ///
    ///     If you need such a range, use `zrange(from:firstIndex:lastIndex:)` instead.
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:indices:includeScoresInResponse:)`.
920
    /// - Parameters:
921 922 923
    ///     - key: The key of the SortedSet to return elements from.
    ///     - range: The range of inclusive indices of elements to get.
    /// - Returns: An array of elements found within the range specified.
924 925
    @inlinable
    public func zrange(
926
        from key: RedisKey,
927 928
        indices range: ClosedRange<Int>,
        includeScoresInResponse includeScores: Bool = false
Nathan Harris's avatar
Nathan Harris committed
929
    ) -> EventLoopFuture<[RESPValue]> {
930
        return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound, includeScoresInResponse: includeScores)
931
    }
932 933 934 935 936 937 938
    
    /// Gets all the elements from a SortedSet starting with the first index bound up to, but not including, the element at the last index bound.
    ///
    /// To get the elements at index 4 through 7:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: 4..<8)
    /// ```
Nathan Harris's avatar
Nathan Harris committed
939
    ///
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063
    /// To get the last 4 elements:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)..<0)
    /// ```
    ///
    /// To get the first and last 4 elements:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)..<4)
    /// ```
    ///
    /// To get the first element, and the last 4:
    /// ```swift
    /// client.zrange(from: "mySortedSet", indices: (-4)..<1)
    /// ```
    ///
    /// See [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
    /// - Warning: A `Range` cannot be created where `upperBound` is less than `lowerBound`; so while Redis may support `0..<(-1)`,
    ///     `Range` will trigger a precondition failure.
    ///
    ///     If you need such a range, use `zrange(from:firstIndex:lastIndex:)` instead.
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:indices:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - range: The range of indices (inclusive lower, exclusive upper) elements to get.
    /// - Returns: An array of elements found within the range specified.
    @inlinable
    public func zrange(
        from key: RedisKey,
        indices range: Range<Int>,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound - 1, includeScoresInResponse: includeScores)
    }
    
    /// Gets all elements from the index specified to the end of a SortedSet.
    ///
    /// To get all except the first 2 elements of a SortedSet:
    /// ```swift
    /// client.zrange(from: "mySortedSet", fromIndex: 2)
    /// ```
    ///
    /// To get the last 4 elements of a SortedSet:
    /// ```swift
    /// client.zrange(from: "mySortedSet", fromIndex: -4)
    /// ```
    ///
    /// See `zrange(from:indices:)`, `zrange(from:firstIndex:lastIndex:)`, and [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:fromIndex:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - index: The index of the first element that will be in the returned values.
    /// - Returns: An array of elements from the SortedSet between the index and the end.
    @inlinable
    public func zrange(
        from key: RedisKey,
        fromIndex index: Int,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self.zrange(from: key, firstIndex: index, lastIndex: -1, includeScoresInResponse: includeScores)
    }
    
    /// Gets all elements from the start of a SortedSet up to, and including, the element at the index specified.
    ///
    /// To get the first 3 elements of a SortedSet:
    /// ```swift
    /// client.zrange(from: "mySortedSet", throughIndex: 2)
    /// ```
    ///
    /// To get all except the last 3 elements of a SortedSet:
    /// ```swift
    /// client.zrange(from: "mySortedSet", throughIndex: -4)
    /// ```
    ///
    /// See `zrange(from:indices:)`, `zrange(from:firstIndex:lastIndex:)`, and [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:throughIndex:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - index: The index of the last element that will be in the returned values.
    /// - Returns: An array of elements from the start of a SortedSet to the index.
    @inlinable
    public func zrange(
        from key: RedisKey,
        throughIndex index: Int,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self.zrange(from: key, firstIndex: 0, lastIndex: index, includeScoresInResponse: includeScores)
    }
    
    /// Gets all elements from the start of a SortedSet up to, but not including, the element at the index specified.
    ///
    /// To get the first 3 elements of a List:
    /// ```swift
    /// client.zrange(from: "myList", upToIndex: 3)
    /// ```
    ///
    /// To get all except the last 3 elements of a List:
    /// ```swift
    /// client.zrange(from: "myList", upToIndex: -3)
    /// ```
    ///
    /// See `zrange(from:indices:)`, `zrange(from:upToIndex:lastIndex:)`, and [https://redis.io/commands/zrange](https://redis.io/commands/zrange)
    /// - Important: This treats the SortedSet as ordered from **low** to **high**.
    ///
    /// For the inverse, see `zrevrange(from:upToIndex:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - index: The index of the last element to not include in the returned values.
    /// - Returns: An array of elements from the start of the SortedSet and up to the index.
    @inlinable
    public func zrange(
        from key: RedisKey,
        upToIndex index: Int,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self.zrange(from: key, firstIndex: 0, lastIndex: index - 1, includeScoresInResponse: includeScores)
    }
    
    /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices.
1064 1065
    ///
    /// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
1066 1067 1068
    /// - Important: This treats the SortedSet as ordered from **high** to **low**.
    ///
    /// For the inverse, see `zrange(from:firstIndex:lastIndex:includeScoresInResponse:)`.
1069
    /// - Parameters:
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247
    ///     - key: The key of the SortedSet
    ///     - firstIndex: The index of the first element to include in the range of elements returned.
    ///     - lastIndex: The index of the last element to include in the range of elements returned.
    /// - Returns: An array of elements found within the range specified.
    @inlinable
    public func zrevrange(
        from key: RedisKey,
        firstIndex: Int,
        lastIndex: Int,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self._zrange(command: "ZREVRANGE", key, firstIndex, lastIndex, includeScores)
    }
    
    /// Gets all elements from a SortedSet within the specified inclusive bounds of 0-based indices.
    ///
    /// To get the elements at index 4 through 7:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: 4...7)
    /// ```
    ///
    /// To get the last 4 elements:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)...(-1))
    /// ```
    ///
    /// To get the first and last 4 elements:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)...3)
    /// ```
    ///
    /// To get the first element, and the last 4:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)...0))
    /// ```
    ///
    /// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
    /// - Warning: A `ClosedRange` cannot be created where `upperBound` is less than `lowerBound`; so while Redis may support `0...-1`,
    ///     `ClosedRange` will trigger a precondition failure.
    ///
    ///     If you need such a range, use `zrevrange(from:firstIndex:lastIndex:)` instead.
    /// - Important: This treats the SortedSet as ordered from **high** to **low**.
    ///
    /// For the inverse, see `zrange(from:indices:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - range: The range of inclusive indices of elements to get.
    /// - Returns: An array of elements found within the range specified.
    @inlinable
    public func zrevrange(
        from key: RedisKey,
        indices range: ClosedRange<Int>,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return self.zrange(from: key, firstIndex: range.lowerBound, lastIndex: range.upperBound, includeScoresInResponse: includeScores)
    }
    
    /// Gets all the elements from a SortedSet starting with the first index bound up to, but not including, the element at the last index bound.
    ///
    /// To get the elements at index 4 through 7:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: 4..<8)
    /// ```
    ///
    /// To get the last 4 elements:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)..<0)
    /// ```
    ///
    /// To get the first and last 4 elements:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)..<4)
    /// ```
    ///
    /// To get the first element, and the last 4:
    /// ```swift
    /// client.zrevrange(from: "mySortedSet", indices: (-4)..<1)
    /// ```
    ///
    /// See [https://redis.io/commands/zrevrange](https://redis.io/commands/zrevrange)
    /// - Warning: A `Range` cannot be created where `upperBound` is less than `lowerBound`; so while Redis may support `0..<(-1)`,
    ///     `Range` will trigger a precondition failure.
    ///
    ///     If you need such a range, use `zrevrange(from:firstIndex:lastIndex:)` instead.
    /// - Important: This treats the SortedSet as ordered from **high** to **low**.
    ///
    /// For the inverse, see `zrange(from:indices:includeScoresInResponse:)`.
    /// - Parameters:
    ///     - key: The key of the SortedSet to return elements from.
    ///     - range: The range of indices (inclusive lower, exclusive upper) elements to get.
    /// - Returns: An array of elements found within the range specified.
    @inlinable
    public func zrevrange(
        from key: RedisKey,
        indices range: Range<Int>,
        includeScoresInResponse includeScores: Bool = false
    ) -> EventLoopFuture<[RESPValue]> {
        return<